docker,Dockerfile,docker-compose操作指南及最佳实践

容器管理工具docker的出现使开发及运行环境的配置变得更加便利,服务器运维更高效,也成为了后端工程师必须要掌握的效率工具,我们在这里总结一下它的具体使用,关于Linux下安装docker相关请看前面一篇:CentOS7安装Docker配置服务端和容器自启动,这里我们将详细介绍docker的使用,dockerfile的编写规则和实例,docker-compose工具的使用和实例,基本上想学会docker,看这一篇就够了。

Docker基本操作

镜像操作

配置阿里云镜像加速器

为了提高官方镜像拉取速度,我们使用阿里云提供的镜像加速器。

注册一个阿里云账号

进入容器镜像服务 => 镜像加速器,获取自己的加速器地址,替换到下面,运行下面的命令即可。

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://*****.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

拉取镜像

比如我们要拉取php官方镜像,先到hub.docker.com上找到我们想要的php镜像版本(tags),可以看到,这里有非常多的满足不同需求的版本镜像,我们选7.1-fpm`,我们的拉取命令就是:

docker pull php:7.1-fpm

不指定taghub拉取镜像可能会报错,比如:

[root@localhost docker]# docker pull opensips/opensips
Using default tag: latest
Error response from daemon: manifest for opensips/opensips:latest not found

这里是因为pull默认使用latest作为镜像tag,但这个镜像下没有这个未指定这个tag,所以我们需要手动指定一个存在的tag拉取。

[root@localhost docker]# docker pull opensips/opensips:2.4

拉取的镜像并不总能直接满足需求,大部分时候我们需要创建自定义镜像,看下面的Dockerfile的使用部分。

提交镜像

第一步:在https://hub.docker.com/注册账户并创建仓库;
第二步: 为镜像设置tag
比如我要把本地的php71镜像提交到仓库hsu1943/php71:latest

docker tag php:7.1 hsu1943/php71:latest

第三步:推送镜像:

docker push hsu1943/php71:latest

容器操作

查看所有容器(包括已停止的)

[root@localhost /]# docker ps -a
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS                     PORTS                                      NAMES
03861a98f878        php:7.1                 "docker-php-entrypoi…"   11 days ago         Up 33 minutes              0.0.0.0:9000->9000/tcp                     php71
d3e84fdcd630        nginx:1.14              "nginx -g 'daemon of…"   12 days ago         Up 33 minutes              0.0.0.0:80->80/tcp, 0.0.0.0:443->433/tcp   nginx114
0d47cf1170a8        redis:4.0               "docker-entrypoint.s…"   12 days ago         Up 33 minutes              0.0.0.0:6379->6379/tcp                     redis4
076c3f7b2d07        mysql:5.7               "docker-entrypoint.s…"   12 days ago         Up 33 minutes              0.0.0.0:3306->3306/tcp, 33060/tcp          mysql57
24ecc15d6bda        opensips/opensips:2.4   "/run.sh"                3 weeks ago         Exited (137) 3 weeks ago                                              opensips

重新启动容器,以下的ID指的是上面的CONTAINER ID或者是NAMES

docker restart ID

关闭容器

docker stop ID

强制关闭容器

docker kill ID

删除容器

docker rm ID

查看容器日志

docker logs ID

进入容器并打开终端

docker exec -it ID /bin/bash

查询容器ip地址

docker inspect --format '{{ .NetworkSettings.IPAddress }}' ID

容器中viipconfigping命令报错:

command not found,由于一般镜像未安装这些工具,需要我们安装,当然也可以在自己的镜像Dockfile中加入。

apt-get update
#vim
apt-get install vim
#ipconfig
apt install net-tools
#ping
apt install iputils-ping

设置容器网络:

使用docker network进行网络相关配置

# 新建网络
[root@localhost /]# docker network create --subnet=172.18.0.0/16 mynetwork
c387a6bdaf315fb156bfddea2edbe772bfa2981e55ee5f23bdbf5aa38a669903
# 查看已有网络
[root@localhost /]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
11a662a47a82        bridge              bridge              local
1cde9f1dc93e        host                host                local
c387a6bdaf31        mynetwork           bridge              local
0d42b5bfe5e1        none                null                local
# 查看网络连接情况
[root@localhost /]# docker network inspect mynetwork
# 让容器连接到指定网络指定ip
[root@localhost /]# docker network connect --ip 172.18.0.8 --alias mysql_test mynetwork containerID
# 断开容器网络连接
[root@localhost /]# docker network disconnet mynetwork containerID
# 移除网络,需要该网络中的容器全部断开连接才可以
[root@localhost /]# docker network rm mynetwork

运行容器:

使用docker run直接来运行一个基于官方镜像mysql:5.7的容器,根据参数说明,我们就知道如何启动一个容器了。

docker run -d -p 3306:3306 --privileged=true \
-v /home/docker/mysql/conf/my.cnf:/etc/mysql/my.cnf \
-v /home/docker/mysql/data:/var/lib/mysql \
-v /home/docker/mysql/log:/var/log/mysql \
--restart=always \
--network mynetwork --network-alias mysql \
-e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7

参数说明:
-d 后台运行
-p 开放端口 格式|主机端口:容器端口
--privileged 提供权限
-v 文件挂载
--restart=always 保持在线
--network mynetwork 使用自定义网络mynetwork
--network-alias mysql 网络别名mysql
-e MYSQL_ROOT_PASSWORD=123456 配置环境变量
--name mysql mysql:5.7 容器名称

备份mysql容器数据库:

docker exec mysql sh -c 'exec mysqldump --all-databases -uroot -p"123456"' > /some/path/on/your/host/all-databases.sql

从临时容器中获取配置文件

在容器构建过程中需要配置文件挂载,手上没有现成的配置文件?先跑一个临时容器,把默认的配置文件拷贝出来,然后根据自己的情况编辑好配置文件,挂载到使用的容器中去,就可以大道快速部署的目的了。

$ docker run --name tmp-nginx-container -d nginx
$ docker cp tmp-nginx-container:/etc/nginx/nginx.conf /host/path/nginx.conf
$ docker rm -f tmp-nginx-container

# 这里使用了cp命令,在主机和容器之间拷贝文件

Dockerfile的使用

镜像库里的镜像并不能满足所有人的需求,需要根据不同的项目需要来制作自己的镜像,Dockerfile就很方便的是我们可以以某个镜像为基础(基础镜像),制作一个完全满足自己需求的自定义镜像了供项目需要。具体Dockerfile的使用技巧,可以参考这里:Dockerfile reference,这里大概总结一下常用操作和基本格式。

在这之前,先了解一下创建镜像。

docker build命令

# build命令默认上下文是当前目录
docker build 
# 指定生成镜像的名称以及tag,可以多次指定以生成多个
docker build -t shykes/myapp:1.0.2

docker build的工作原理(上下文)解析,Docker在运行时分为 Docker daemon(服务端守护进程)和客户端工具。Docker daemon提供了一组REST API,被称为DockerRemote API,而如docker命令这样的客户端工具,则是通过这组APIDocker daemon交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种docker功能,但实际上,一切都是使用的远程调用形式在服务端(Docker daemon)完成。也因为这种C/S设计,让我们操作远程服务器的Docker daemon变得轻而易举。

当我们进行镜像构建的时候,并非所有定制都会通过RUN指令完成,经常会需要将一些本地文件复制进镜像,比如通过COPY指令、ADD指令等。docker build命令构建镜像,其实并非在本地构建,而是在服务端,也就是Docker daemon中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?

这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径, docker build命令得知这个路径后,会将路径下的所有内容打包,然后上传给Docker daemon。这样Docker daemon收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

# 会将上下文(context)发送给Docker daemon
docker build .
Sending build context to Docker daemon  6.51 MB
...

docker build命令的上下文还可以是git仓库,压缩文件甚至是文本文件。更多的关于docker build命令相关看这里:docker build

千万不要在根目录之类的目录作为上下文,一般使用一个空目录,把Dockerfile和需要的文件放在该目录下进行创建。

Dockerfile中使用一些操作(比如COPY)将上下文目录中的文件添加到镜像中,可以使用.dockerignore文件指定要忽略的目录或者文件,不知道如何编写一个.dockerignore文件?传送门

默认会使用上下文目录下的Dockerfile,也可以在命令中指定它:

$ docker build -f /path/to/a/Dockerfile .

在执行Dockerfile前,Docker daemon会一条条去校验其中的指令,如果有错误将会提示错误并停止执行。

Docker daemon会一条一条的执行指令,每一条产生一个新的,每一层是独立存在,所以在上一个指令中的cd /tmp对后面的指令没有影响,为了提高执行效率,应该尽量将同一类的指令放到一起执行,减少,在执行过程中,默认会使用缓存,也就是如果之前有执行过,生成的层镜像和现在需要的是一样的,那就直接用缓存了。

Dockerfile格式

#开头后面视为注释(除解析器指令);Dockerfile指令不区分大小写,不过一般约定指令使用大写,与参数区分;

FROM:必须使用FROM指令开头,来指定基础镜像;

  • 镜像可以是任意有效的镜像 – 这个很容易从公共仓库拉取一个镜像。
  • FROM必须是Dockerfile中的首个非注释指令。
  • FROM可以在单个Dockerfile中出现多次,这样可以创建多个镜像。只需记下在每个新的FROM命令之前由提交输出的最后一个图像ID。
  • tag或digest值是可选的。如果都不指定,那么默认是latest。如果找不到tag的值将返回错误。
FROM <image>
FROM <image>:<tag>
FROM <image>@<digest>

MAINTAINER:设置生成这个镜像的作者。

MAINTAINER hsu1943 "88888888@qq.com"

ENV:声明环境变量,可以在其他指令中使用;

  • Dockerfile引用环境变量可以使用$variable_name${variable_name}。它们是等同的,其中大括号的变量是用在没有空格的变量名中的,如${foo}_bar
  • ${variable_name}变量也支持一些标准的bash修饰符,如:
    • ${variable:-word}表示如果variable设置了,那么结果就是设置的值。否则设置值为word
    • ${variable:+word}表示如果variable设置了,那么结果是word值,否则为空值
  • word可以是任意的字符,包括额外的环境变量。
  • 转义符可以添加在变量前面:\$foo 或者 \${foo},例如,会分别转换为$foor${foo}
FROM busybox
ENV foo /bar
ENV PHP_INI_DIR /usr/local/etc/php
WORKDIR ${foo}   # WORKDIR /bar
ADD . $foo       # ADD . /bar
COPY \$foo /quux # COPY $foo /quux

COPY:将上下文目录中的文件复制到docker镜像中

这个命令很直观,也很可靠,在这里大家可能会想到ADD命令,这个命令除了复制文件以外还会做更多(复制远程文件,可以自动解压缩),但我们还是建议直接用COPY就够了,降低复杂度,提高成功率。

对于目录而言,COPY只复制目录中的内容而不包含目录自身,所以当你需要复制包含目录的话,应该指定目标路径目录。

COPY docker-php-source /usr/local/bin/

ENTRYPOINT和CMD:设置容器启动时要执行的命令

这两个命令功能上是很相似的,所以也很容易混淆,基本原则:

  • Dockerfile中至少有一个(ENTRYPOINT或者CMD),不然回会报错;
  • 多个ENTRYPOINT或多个CMD只会执行最后一条;
  • 如果 ENTRYPOINT 使用了 shell 模式,CMD 指令会被忽略;
  • 如果 ENTRYPOINT 使用了 exec 模式,CMD 指定的内容被追加为 ENTRYPOINT指定命令的参数;
  • 如果 ENTRYPOINT 使用了 exec 模式,CMD 也应该使用 exec 模式。

对于 CMDENTRYPOINT 的设计而言,多数情况下它们应该是单独使用的。当然,有一个例外是 CMDENTRYPOINT 提供默认的可选参数。

如果还是不清楚,看看官方给出的这个图,大概就能理解了:

Dockerfile中ENTRYPOINT和CMD的使用

LABEL:添加关于镜像的一些信息

LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
LABEL maintainer="SvenDowideit@home.org.au" # 代替MAINTAINER的使用,推荐

因为每一个LABEL都会生成一个新的镜像层,所以建议将LABEL放到一起,可以使用\换行达到目的,使用docker inspect可以查看镜像的LABELs,对于同名(key)的情况,后面的LABEL会覆盖前面的,包括FROM指定的基础镜像中的,多个LABEL合并一起:

LABEL version="1.2.0" \
      description="about lnmp" \
      maintainer="SvenDowideit@home.org.au" \
      other="other value"

EXPOSE:声明容器运行时应该打开的端口

这个指令告诉要运行docker run这个镜像的运维人员本镜像哪些端口需要映射,在run的之后可以通过-p来指定端口映射,或者用-P随机映射端口,随机映射会将EXPOSE声明过的端口映射出来。

对于在同一个docker network中的容器,无需打开端口他们之间就可以通过任何端口来访问彼此。

EXPOSE 9000

USER:指定执行后面RUN,CMD,ENTRYPOINT的用户(组)

使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。当不需要管理员权限时,可以通过该命令指定运行用户。一般会在之前创建所需要的用户。

运行容器docker run时,可以通过-u参数来覆盖所指定的用户。

WORKDIR:指定工作目录

可以为后面的RUN, CMD, ENTRYPOINT, COPY 以及 ADD指令指定工作目录,目录中可以解析之前使用ENV指定的环境变量。

ENV DIRPATH /var
WORKDIR $DIRPATH/www/html
echo pwd 
# 会输出 /var/www/html

ARG:构建参数,指定在build时可以传递过来的变量

使用ARG指定的变量可以在docker build中通过--build-arg <varname>=<value>的形式进行传递,这区别于ENV指定的环境变量,环境变量无法在build时传递进来。

ARG user1=someuser # 没有传递进来就使用默认值
ARG buildno=1

可以将ARGENV结合一起使用:

ARG var
ENV var=${var}

ONBUILD:构建子镜像时在FROM之后立即执行在的任务

本镜像并不执行这个指令,而是在子镜像(下一层)中去执行。

# 父容器中Dockerfile内容
FROM busybox
ONBUILD RUN echo "第二次构建(子构建)才执行"

# 构建父镜像
docker build -t father .

# 构建过程中不会打印"第二次构建(子构建)才执行"

创建子容器
# 子容器中Dockerfile内容
FORM father

# 构建子镜像
docker build -t child .
# 构架过程中打印了"第二次构建(子构建)才执行"

VOLUME:指定容器数据挂载点

指定容器中需要挂载到宿主机的挂载点,在宿主机会生成对应的目录(目录名随机)存放数据,需要注意这里跟docker run中可以指定宿主机目录不同,Dockerfile中的挂载无法指定宿主机目录,这是为了保证可移植性,规定的目录并不一定在每个宿主机都存在。

VOLUME /var/log/mysql
VOLUME ["/var/log/mysql", "/data2"]

更多关于VOLUME(共享--volumes-from)的知识可以参考官方文档。

Dockerfile小技巧:apt使用国内源

# Dockfile开头加一行,使用国内debian源
COPY ./sources.list /etc/apt/sources.list
# 在Dockerfile同目录新建sources.list,内容:
deb http://mirrors.ustc.edu.cn/debian/ stretch main non-free contrib
deb http://mirrors.ustc.edu.cn/debian/ stretch-updates main non-free contrib
deb http://mirrors.ustc.edu.cn/debian/ stretch-backports main non-free contrib
deb-src http://mirrors.ustc.edu.cn/debian/ stretch main non-free contrib
deb-src http://mirrors.ustc.edu.cn/debian/ stretch-updates main non-free contrib
deb-src http://mirrors.ustc.edu.cn/debian/ stretch-backports main non-free contrib
deb http://mirrors.ustc.edu.cn/debian-security/ stretch/updates main non-free contrib
deb-src http://mirrors.ustc.edu.cn/debian-security/ stretch/updates main non-free contrib
# 不同版本的debian修改stretch即可

Dockerfile实例:

FROM php:7.1-fpm
# 使用国内源
ADD sources.list /etc/apt/
# install any custom system requirements
RUN apt-get update && apt-get install -y --no-install-recommends \
        libfreetype6-dev \
        libjpeg-dev \
        libjpeg62-turbo-dev \
        libmcrypt-dev \
        libpng-dev \
        libpq-dev \
        libicu-dev \
        libz-dev \
        libbz2-dev \
        libmemcached-dev \
        libxml2-dev \
        git \
    && rm -rf /var/lib/apt/lists/*

# Install various PHP extensions
RUN docker-php-ext-configure bcmath --enable-bcmath \
    && docker-php-ext-configure pcntl --enable-pcntl \
    && docker-php-ext-configure pdo_mysql --with-pdo-mysql \
    && docker-php-ext-configure pdo_pgsql --with-pdo-pgsql \
    && docker-php-ext-configure mbstring --enable-mbstring \
    && docker-php-ext-configure soap --enable-soap \
    && docker-php-ext-install \
        bcmath \
        intl \
        mbstring \
        mcrypt \
        mysqli \
        pcntl \
        pdo_mysql \
        pdo_pgsql \
        soap \
        sockets \
        zip \
          iconv \
  && docker-php-ext-configure gd \
    --enable-gd-native-ttf \
    --with-jpeg-dir=/usr/lib \
    --with-freetype-dir=/usr/include/freetype2 \
  && docker-php-ext-install gd \
  && docker-php-ext-install opcache \
  && docker-php-ext-enable opcache \
  && pecl install xdebug redis \
  && docker-php-ext-enable xdebug redis \
  && rm -rf /tmp/pear

# AST
RUN git clone https://github.com/nikic/php-ast /usr/src/php/ext/ast/ \
    && docker-php-ext-configure ast \
    && docker-php-ext-install ast

# ICU - intl requirements for Symfony 由于下载巨慢,改成直接COPY压缩包
# Debian is out of date, and Symfony expects the latest - so build from source, unless a better alternative exists(?)
# RUN curl -o /tmp/icu.tar.gz -L http://download.icu-project.org/files/icu4c/63.1/icu4c-63_1-src.tgz \
COPY ./icu4c-58_2-src.tgz /tmp/icu.tar.gz
RUN tar -zxf /tmp/icu.tar.gz -C /tmp \
    &&  ( \
      cd /tmp/icu/source \
      && ./configure --prefix=/usr/local \
      && make \
      && make install \
    ) \
    && rm -rf /tmp/icu \
    && rm /tmp/icu.tar.gz \
    && docker-php-ext-configure intl --with-icu-dir=/usr/local \
    && docker-php-ext-install intl

# Install the php memcached extension
RUN curl -L -o /tmp/memcached.tar.gz "https://github.com/php-memcached-dev/php-memcached/archive/php7.tar.gz" \
  && mkdir -p memcached \
  && tar -C memcached -zxvf /tmp/memcached.tar.gz --strip 1 \
  && ( \  
    cd memcached \
    && phpize \
    && ./configure \
    && make -j$(nproc) \
    && make install \
  ) \
  && rm -r memcached \
  && rm /tmp/memcached.tar.gz \
  && docker-php-ext-enable memcached


# set timezone
RUN rm /etc/localtime \
  && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

#composer
RUN curl -sS https://getcomposer.org/installer | php -- \
    --install-dir=/usr/bin \
    --filename=composer

这个PHP7.1Dockerfile以官方镜像php:7.1-fpm为计出镜像,安装了常用的工具及扩展,设置时区及composer,开包即用,非常方便。

为了更好的理解build命令的上下文,看一下当前目录结构:

[root@localhost docker]# tree php7
php7
├── Dockerfile
├── icu4c-58_2-src.tgz
└── sources.list
0 directories, 3 files

docker-compose的使用

用于定义和运行多个docker容器的工具,为运维工作服务器的部署和自动化运维提供了极大的便利,通过一个docker-compose.yml命令就可以实现多个

安装docker-compose

安装

curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

添加执行权限

chmod +x /usr/local/bin/docker-compose

关于docker-compose的使用,以及docker-compose.yml文件的书写格式,见:
docker-compose官方文档

docker-compose.yml实例

version: '3'
services:
    nginx:
        image: nginx:1.14
        restart: always
        # 端口映射
        ports:
            - "80:80"
            - "443:433"
        environment:
            - TZ=Asia/Shanghai
        # 依赖关系
        depends_on:
            - "php"
        # 数据卷
        volumes:
            # 映射主机./conf.d目录wwwroot目录nginx.conf文件
            - $PWD/nginx/conf.d:/etc/nginx/conf.d
            - $PWD/wwwroot:/usr/share/nginx/html
            - $PWD/nginx/nginx.conf:/etc/nginx/nginx.conf
            - $PWD/logs/nginx:/var/log/nginx
        networks:
            - app_net
        # 容器名称
        container_name: "nginx114"
    php:
        build: ./php7
        restart: always
        # image指定build 上下文./php7目录下Dockerfile生成镜像的名称
        image: php:7.1
        # php-fpm记录日志用
        cap_add:
            - SYS_PTRACE
        ports:
            - "9000:9000"
        volumes:
            # 网站文件目录,配置目录
            - $PWD/wwwroot:/var/www/html
            - $PWD/php:/usr/local/etc
        networks:
            - app_net
        container_name: "php71"
    mysql:
        image: mysql:5.7
        restart: always
        ports:
            - "3306:3306"
        # 环境变量
        environment:
            # mysql密码
            - TZ=Asia/Shanghai
            - MYSQL_ROOT_PASSWORD=123456
        volumes:
            # 挂载数据库文件,配置文件
            - $PWD/mysql/data:/var/lib/mysql
            - $PWD/mysql/conf:/etc/mysql
            - $PWD/logs/mysql:/var/log/mysql
        networks:
            - app_net
        container_name: "mysql57"
    redis:
        image: redis:4.0
        restart: always
        # 如果不需要外网访问,使用"127.0.0.1:6379:6379"来限制
        ports:
            - "6379:6379"
        environment:
            - TZ=Asia/Shanghai
        volumes:
            - $PWD/redis/data:/data
            - $PWD/redis/redis.conf:/etc/redis/redis.conf
        networks:
            - app_net
        container_name: "redis4"
networks:
    # 配置docker network
    app_net:
        driver: bridge
        ipam:
            config:
                # 子网络
                - subnet: 178.18.0.0/16

这是一个完整的nginx + php + mysql + redis多容器的运行环境,结合上面php7.1Dockerfile实例只需要按需要修改挂载目录及配置文件即可运行。

4条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注