《Docker学习笔记——基础篇》已经记录了容器与镜像的核心概念、安装、基本命令、docker run和docker inspect的详细用法。这篇接着往下,把日常开发和部署中绑定更紧的几块记下来:网络、数据卷、Dockerfile构建镜像、镜像分发,以及用Compose编排多容器服务。
网络
容器默认各自有独立的网络命名空间,彼此隔离。但实际用起来,容器之间要互相通信(比如应用连数据库)、容器要对外提供服务(比如把端口映射到宿主机),这些都靠Docker网络打通。Docker启动时会自动建一个名为docker0的虚拟网桥,默认情况下容器都接在它上面。
网络模式
Docker通过网络驱动(driver)提供几种网络模式,常用的几种如下:
| 模式 |
驱动 |
说明 |
适用场景 |
| bridge |
bridge |
默认模式,容器接到虚拟网桥docker0,通过NAT与外界通信 |
单机多容器,最常用 |
| host |
host |
容器直接共享宿主机网络栈,不做网络隔离,容器端口即宿主机端口 |
追求网络性能、不在意隔离 |
| none |
null |
只保留lo回环,没有任何对外网络 |
完全不需要网络的容器 |
| container |
- |
复用另一个容器的网络命名空间,两者共享同一IP和端口 |
边车(sidecar)、调试 |
| overlay |
overlay |
跨主机的容器网络,让多台机器上的容器互通 |
Swarm、集群 |
| macvlan |
macvlan |
给容器分配独立MAC,使它像物理机一样直接出现在局域网 |
容器需独立IP接入物理网络 |
用--network指定模式,不写默认就是bridge:
1 2 3
| docker run -d --network host nginx docker run -d --network none busybox docker run -d --network container:web app
|
入门阶段最常打交道的是bridge,host和none偶尔用;overlay、macvlan等到集群或特殊组网时再深入即可。
默认 bridge 与自定义 bridge
同样是bridge,Docker自带的docker0和自己docker network create出来的自定义网桥,有一个关键差别——容器之间能不能用名字互相访问。
| 对比项 |
默认bridge(docker0) |
自定义bridge |
| 容器名DNS解析 |
不支持,只能用IP互访 |
支持,直接用容器名访问 |
| 创建方式 |
Docker自带,无需创建 |
需docker network create |
| 网络隔离 |
所有默认容器都在同一网段 |
不同自定义网络之间互相隔离 |
| 互访方式 |
老式--link(已不推荐) |
同网络内开箱即用 |
说白了,自定义bridge内置了DNS,容器A可以直接用容器B的名字连它,不用关心对方IP重启后会不会变。这也是官方推荐用自定义网络、而非默认bridge加--link的主要原因。
docker network 常用命令
| 命令 |
作用 |
docker network ls |
列出所有网络 |
docker network create mynet |
创建网络(默认bridge驱动,可加-d overlay等指定驱动) |
docker network inspect mynet |
查看网络详情(网段、网关、已连接的容器等) |
docker network connect mynet web |
把已运行的容器web接入mynet |
docker network disconnect mynet web |
把web从mynet断开 |
docker network rm mynet |
删除网络(需先断开其上所有容器) |
docker network prune |
清理所有未被使用的网络 |
docker network ls默认能看到三个内置网络:bridge、host、none,分别对应前面三种模式,它们删不掉。
查看网络列表
一个容器还可以同时接入多个网络——用docker network connect把运行中的容器再挂到别的网络上,常用于让某个容器既能在内网通信、又能访问另一组服务。
数据卷
容器的文件系统由镜像的只读层加上层一个可写层组成,数据默认写在这个可写层里。可写层和容器同生共死——容器一删,里面攒下的数据就全没了。所以但凡要持久化数据(数据库文件、上传的内容)、或者要在宿主机与容器之间、容器与容器之间共享文件,就得把数据放到可写层之外,这就是挂载(mount)要解决的事。
数据卷
挂载点之下的数据不进容器可写层,而是落到独立的卷或宿主机目录里,所以删容器不影响它,新容器挂上去就能接着用。
三种挂载方式
| 方式 |
数据位置 |
由谁管理 |
典型场景 |
| volume(卷) |
/var/lib/docker/volumes/<卷名>/_data |
Docker |
持久化生产数据(如数据库),首选 |
| bind mount(绑定挂载) |
宿主机任意指定路径 |
用户自己 |
开发时挂源码、配置文件进容器 |
| tmpfs mount |
宿主机内存,不落盘 |
Docker |
临时、敏感数据,容器停止即消失(仅 Linux) |
三者里 volume 是官方最推荐的——位置由 Docker 统一管理、不依赖宿主机目录结构、可移植,也能用 docker volume 命令成体系地管理。bind mount 胜在直接,想改宿主机上哪个文件就挂哪个,开发期最顺手。
volume 与 bind mount 的区别
这两个最常用,也最容易混,单独拉出来对比:
| 维度 |
volume(命名卷) |
bind mount(绑定挂载) |
| 宿主机路径 |
由 Docker 决定,固定在 volumes 目录 |
自己指定任意绝对路径 |
| 可移植性 |
强,换台机器照样建同名卷 |
弱,依赖宿主机有对应目录 |
| 初次挂载时 |
若卷为空且容器内目标路径已有数据,会把镜像里那份数据复制进卷 |
直接用宿主机目录覆盖,容器内原有内容被遮盖 |
| 路径不存在时 |
卷不存在会自动创建 |
-v 自动把宿主机路径建成目录;--mount 直接报错 |
| 适用 |
生产数据持久化 |
开发挂代码 / 配置 |
那条「volume 为空时复制镜像数据、bind mount 直接遮盖」是最容易踩的坑:用命名卷挂 MySQL 数据目录,首次能拿到镜像里初始化好的库;换成 bind mount 挂一个空目录,容器里那份初始数据就被空目录盖掉了。
挂载语法
有两种写法,-v 简写和 --mount 显式,效果基本一致,--mount 更啰嗦但语义清楚、脚本里更不易错。
| 写法 |
示例 |
说明 |
-v 卷名:容器路径 |
-v mydata:/var/lib/mysql |
命名卷 |
-v 宿主机路径:容器路径 |
-v /home/data:/service/data |
bind mount(路径以 / 开头) |
-v 容器路径 |
-v /data |
匿名卷,Docker 随机起名 |
结尾加 :ro |
-v mydata:/data:ro |
容器内只读 |
--mount type=volume,... |
--mount type=volume,src=mydata,dst=/data |
显式命名卷 |
--mount type=bind,... |
--mount type=bind,src=/home/data,dst=/data,readonly |
显式 bind,可加 readonly |
--mount type=tmpfs,... |
--mount type=tmpfs,dst=/tmp/cache |
挂内存盘 |
1 2 3 4 5 6 7 8 9
| docker run -d --name db -v mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8
docker rm -f db docker run -d --name db2 -v mysql_data:/var/lib/mysql mysql:8
docker run -d -p 8080:80 -v /home/cuixiaogang/www:/usr/share/nginx/html:ro nginx
|
匿名卷(-v /data 不给名字)要留意:Docker 会生成一长串随机名的卷,容器删了卷常残留下来,时间长了攒一堆没人认领的卷。docker run --rm 退出时会顺手清掉容器挂的匿名卷,但不会动命名卷——命名卷得自己显式删。
docker volume 常用命令
| 命令 |
作用 |
docker volume create mydata |
创建命名卷 |
docker volume ls |
列出所有卷 |
docker volume inspect mydata |
查看卷详情(含宿主机上的真实路径 Mountpoint) |
docker volume rm mydata |
删除卷(卷被容器占用时删不掉) |
docker volume prune |
清理所有未被任何容器使用的卷 |
想知道某个卷在宿主机上到底存在哪,docker volume inspect 输出里的 Mountpoint 就是,一般是 /var/lib/docker/volumes/mydata/_data。命名卷用之前不必先create,docker run -v mydata:/path时若卷不存在会自动建。
卷与容器的生命周期
卷的生命周期独立于容器,这正是它能持久化的原因:
- 删容器(
docker rm)默认不会删卷,数据还在,新容器挂同名卷即可接着用。
docker rm -v 容器才会连带删掉它的匿名卷(命名卷依然保留)。
- 想彻底清掉没人用的卷,用
docker volume prune。
Dockerfile
Dockerfile是一个文本文件,用一条条指令描述「从某个基础镜像出发,一步步构建出自己的镜像」的全过程。docker build读取它来生成镜像。每条指令大致对应镜像里的一个只读层(layer),层会被缓存复用,这是重复构建很快的原因。
指令一览
下面是Dockerfile的全部指令关键字:
| 指令 |
作用 |
示例 |
FROM |
指定基础镜像,必须是第一条(ARG除外) |
FROM golang:1.24 |
RUN |
构建时执行命令,常用于装软件、编译 |
RUN apt-get update && apt-get install -y git |
CMD |
容器启动时的默认命令,可被docker run后面的参数覆盖 |
CMD ["./app"] |
ENTRYPOINT |
容器启动入口,不会被run参数轻易覆盖,常和CMD配合 |
ENTRYPOINT ["nginx"] |
COPY |
把构建上下文里的文件复制进镜像 |
COPY . /app |
ADD |
类似COPY,但额外支持自动解压本地tar、拉远程URL |
ADD app.tar.gz /app |
ENV |
设置环境变量,构建期和运行期都有效 |
ENV PORT=8080 |
ARG |
构建期变量,仅build时有效,可被--build-arg传入 |
ARG VERSION=1.0 |
WORKDIR |
设置工作目录,后续指令的相对路径都基于它 |
WORKDIR /app |
EXPOSE |
声明容器监听的端口,仅声明不等于映射 |
EXPOSE 80 |
VOLUME |
声明一个挂载点(匿名卷) |
VOLUME /data |
USER |
指定后续指令及容器运行时的用户 |
USER 1000 |
LABEL |
给镜像加元数据标签 |
LABEL maintainer="me@x.com" |
HEALTHCHECK |
定义容器健康检查命令 |
HEALTHCHECK CMD curl -f http://localhost || exit 1 |
ONBUILD |
触发器,本镜像被当作基础镜像时才执行 |
ONBUILD COPY . /app |
STOPSIGNAL |
指定停止容器时发送的系统信号 |
STOPSIGNAL SIGTERM |
SHELL |
覆盖RUN/CMD默认使用的shell |
SHELL ["/bin/bash", "-c"] |
MAINTAINER |
标注作者,已废弃,用LABEL代替 |
MAINTAINER me |
几个易混点
CMD与ENTRYPOINT:ENTRYPOINT定义固定入口,CMD给默认参数。docker run后面跟的参数会覆盖CMD,但不会覆盖ENTRYPOINT。两者都建议用exec形式(JSON数组)写。
ADD与COPY:优先用COPY,语义单纯、可预期;只有确实需要自动解压本地tar时才用ADD,不推荐用它拉远程URL。
- 每条
RUN/COPY/ADD都会新增一层,把多条RUN用&&合并能减少层数、缩小镜像。
- shell形式(
RUN apt-get update)与exec形式(RUN ["apt-get","update"]):CMD/ENTRYPOINT推荐exec形式,避免被sh -c包裹导致信号传不进主进程。
几个常见的Dockfile示例
开发环境:PHP-7.1.30
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
| FROM harbor.qihoo.net/ybjcjdz-mobile-url-collection/centos:7_v1
LABEL maintainer="cuixiaogang@360.cn"
RUN yum install -y openssl openssl-devel libcurl libcurl-devel libmcrypt libmcrypt-devel zlib zlib-devel libxml2 libxml2-devel curl boost-devel bzip2 bzip2-devel libcurl libcurl-devel libxslt libxslt-devel pcre pcre-devel wget make gcc gcc-c++ cmake autoconf vim net-tools \ && rm -rf /var/cache/yum \ && yum clean all \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && mkdir -p /service/ops \ && cd /service/ops \ && mkdir -p /service/app/php-7.1.30 \ && wget --no-check-certificate https://www.php.net/distributions/php-7.1.30.tar.gz \ && cd /service/ops \ && tar -zxvf php-7.1.30.tar.gz \ && mkdir /service/app/php-7.1.30/run \ && mkdir /service/app/php-7.1.30/logs \ && mkdir /service/app/php-7.1.30/etc \ && mkdir /service/app/php-7.1.30/etc/scan.d \ && cd /service/ops/php-7.1.30 \ && ./configure --prefix=/service/app/php-7.1.30 --with-config-file-path=/service/app/php-7.1.30/etc/ \ --with-config-file-scan-dir=/service/app/php-7.1.30/etc/scan.d/ --enable-fpm --with-fpm-group=nobody \ --disable-rpath --with-openssl -with-libxml-dir --with-xmlrpc --with-mhash --with-pcre-regex --with-zlib --enable-bcmath \ --with-bz2 --enable-calendar --with-curl --with-gettext --with-openssl-dir --with-zlib-dir --with-mysqli=mysqlnd \ --with-pdo-mysql=mysqlnd --enable-mbstring --with-xsl --enable-zip --with-pear --enable-shmop --enable-sockets --enable-sysvmsg \ --enable-sysvsem --enable-sysvshm --enable-wddx \ && make && make install
ENV PATH=$PATH:/service/app/php-7.1.30/bin:/service/app/php-7.1.30/sbin
RUN cd /service/ops/ \ && wget --no-check-certificate https://github.com/alanxz/rabbitmq-c/archive/refs/tags/v0.7.1.tar.gz \ && mv v0.7.1.tar.gz rabbitmq-c-0.7.1.tar.gz \ && tar -zxvf rabbitmq-c-0.7.1.tar.gz \ && cd rabbitmq-c-0.7.1 \ && mkdir build && cd build \ && cmake -DCMAKE_INSTALL_PREFIX=/usr/local/rabbitmq-c-0.7.1 .. \ && cmake --build . --target install \ && ln -s /usr/local/rabbitmq-c-0.7.1/lib64 /usr/local/rabbitmq-c-0.7.1/lib \ && cd /service/ops/php-7.1.30/ext/ \ && wget --no-check-certificate http://pecl.php.net/get/amqp-1.9.4.tgz \ && tar -zxvf amqp-1.9.4.tgz && cd amqp-1.9.4 && phpize \ && ./configure --with-php-config=/service/app/php-7.1.30/bin/php-config --with-amqp --with-librabbitmq-dir=/usr/local/rabbitmq-c-0.7.1 \ && make && make install \ && echo "extension=amqp.so" &>> /service/app/php-7.1.30/etc/scan.d/amqp.ini \ && cd /service/ops/php-7.1.30/ext/ \ && wget --no-check-certificate http://pecl.php.net/get/mongodb-1.9.1.tgz \ && tar -zxvf mongodb-1.9.1.tgz && cd mongodb-1.9.1 && phpize \ && ./configure --with-php-config=/service/app/php-7.1.30/bin/php-config \ && make && make install \ && echo "extension=mongodb.so" &>> /service/app/php-7.1.30/etc/scan.d/mongodb.ini \ && yum install -y librdkafka librdkafka-devel \ && cd /service/ops/php-7.1.30/ext/ \ && wget --no-check-certificate http://pecl.php.net/get/rdkafka-4.1.2.tgz \ && tar -zxvf rdkafka-4.1.2.tgz && cd rdkafka-4.1.2 && phpize \ && ./configure --with-php-config=/service/app/php-7.1.30/bin/php-config \ && make && make install \ && echo "extension=rdkafka.so" &>> /service/app/php-7.1.30/etc/scan.d/rdkafka.ini \ && cd /service/ops/php-7.1.30/ext/ \ && wget --no-check-certificate http://pecl.php.net/get/redis-5.3.4.tgz \ && tar -zxvf redis-5.3.4.tgz && cd redis-5.3.4 && phpize \ && ./configure --with-php-config=/service/app/php-7.1.30/bin/php-config \ && make && make install \ && echo "extension=redis.so" &>> /service/app/php-7.1.30/etc/scan.d/redis.ini \ && yum install -y libmemcached libmemcached-devel unzip zip \ && cd /service/ops/php-7.1.30/ext/ \ && wget --no-check-certificate https://pecl.php.net/get/memcache-4.0.5.1.tgz \ && tar -zxvf memcache-4.0.5.1.tgz && cd memcache-4.0.5.1 && phpize \ && ./configure --with-php-config=/service/app/php-7.1.30/bin/php-config \ && make && make install \ && echo "extension=memcache.so" &>> /service/app/php-7.1.30/etc/scan.d/memcache.ini \ && cd /service/ops/php-7.1.30/ext/pcntl \ && phpize && ./configure --with-php-config=/service/app/php-7.1.30/bin/php-config \ && make && make install \ && echo "extension=pcntl.so" &>> /service/app/php-7.1.30/etc/scan.d/pcntl.ini \ && yum install -y libuuid libuuid-devel \ && cd /service/ops/php-7.1.30/ext/ \ && wget --no-check-certificate https://pecl.php.net/get/uuid-1.2.0.tgz \ && tar -zxvf uuid-1.2.0.tgz && cd uuid-1.2.0 && phpize \ && ./configure --with-php-config=/service/app/php-7.1.30/bin/php-config \ && make && make install \ && echo "extension=uuid.so" &>> /service/app/php-7.1.30/etc/scan.d/uuid.ini \ && echo "extension=qlogphp.so" &>> /service/app/php-7.1.30/etc/scan.d/qlogphp.ini
COPY ./files/qlogphp.so /service/app/php-7.1.30/lib/php/extensions/no-debug-non-zts-20160303/qlogphp.so COPY ./files/lib64/libqlog.so.2 /lib64/libqlog.so.2 COPY ./files/lib64/liblog4cplus-1.1.so.9 /lib64/liblog4cplus-1.1.so.9
RUN curl -sS https://getcomposer.org/installer | php \ && mv composer.phar /usr/bin/composer
RUN mkdir -p /service/app/nginx-1.16.1 \ && cd /service/ops \ && wget --no-check-certificate http://nginx.org/download/nginx-1.16.1.tar.gz \ && tar -zxvf nginx-1.16.1.tar.gz \ && cd nginx-1.16.1 \ && ./configure --user=root --group=root --prefix=/service/app/nginx-1.16.1/ \ --pid-path=/service/app/nginx-1.16.1/run/nginx.pid --lock-path=/service/app/nginx-1.16.1/run/nginx.lock \ --with-http_stub_status_module --with-http_ssl_module --with-stream --with-http_gzip_static_module --with-http_sub_module \ && make && make install
ENV PATH=$PATH:/service/app/nginx-1.16.1/sbin
RUN yum install -y openssh-server \ && echo 'root:4mCrjpI2si7hVJLPv6zMe' | chpasswd \ && sed -i 's/#Port 22/Port 15001/' /etc/ssh/sshd_config \ && sed -i 's/PermitRootLogin no/PermitRootLogin yes/' /etc/ssh/sshd_config \ && sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config \ && ssh-keygen -A \ && mkdir -p /root/.ssh \ && echo "ssh-rsa xxxxxxxxxxxxxx" >> /root/.ssh/authorized_keys
RUN mkdir /service/run
COPY ./config/nginx.conf /service/app/nginx-1.16.1/conf/nginx.conf COPY ./config/php.ini /service/app/php-7.1.30/etc/php.ini COPY ./config/php-fpm.conf /service/app/php-7.1.30/etc/php-fpm.conf COPY ./run.sh /service/run.sh
EXPOSE 15001-15200
VOLUME "/service/www" VOLUME "/service/conf" VOLUME "/service/logs" VOLUME "/service/data"
WORKDIR "/service/data"
RUN chmod 777 -R /service/run.sh \ && echo "export PATH=$PATH" >> /root/.bashrc
CMD ["/service/run.sh"]
|
开发环境:PHP-5.5.25
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
| FROM harbor.qihoo.net/ybjcjdz-mobile-url-collection/centos:7_v1
LABEL maintainer="cuixiaogang@360.cn"
RUN rm -rf /etc/localtime \ && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ &&yum install -y wget make cmake gcc gcc-c++ autoconf libxml2 libxml2-devel openssl openssl-devel bzip2 bzip2-devel libcurl libcurl-devel libxslt libxslt-devel \ && mkdir -p /service/ops \ && cd /service/ops \ && mkdir -p /service/app/php-5.5.25 \ && wget --no-check-certificate https://www.php.net/distributions/php-5.5.25.tar.gz \ && tar -zxvf php-5.5.25.tar.gz \ && mkdir /service/app/php-5.5.25/run \ && mkdir /service/app/php-5.5.25/logs \ && mkdir /service/app/php-5.5.25/etc \ && mkdir /service/app/php-5.5.25/etc/scan.d \ && cd /service/ops/php-5.5.25 \ && ./configure --prefix=/service/app/php-5.5.25 --with-config-file-path=/service/app/php-5.5.25/etc/ --with-config-file-scan-dir=/service/app/php-5.5.25/etc/scan.d/ \ --enable-fpm --with-fpm-group=nobody --disable-rpath --with-openssl -with-libxml-dir --with-xmlrpc --with-mhash --with-pcre-regex --with-zlib --enable-bcmath --with-bz2 \ --enable-calendar --with-curl --with-gettext --with-openssl-dir --with-zlib-dir --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --enable-mbstring \ --with-xsl --enable-zip --with-pear --enable-shmop --enable-sockets --enable-sysvmsg --enable-sysvsem --enable-sysvshm --enable-wddx \ && make && make install
ENV PATH=$PATH:/service/app/php-5.5.25/bin
RUN cd /service/ops/ \ && wget https://github.com/alanxz/rabbitmq-c/releases/download/v0.5.2/rabbitmq-c-0.5.2.tar.gz \ && tar -zxvf rabbitmq-c-0.5.2.tar.gz \ && cd rabbitmq-c-0.5.2 \ && mkdir build && cd build \ && cmake -DCMAKE_INSTALL_PREFIX=/usr/local/rabbitmq-c-0.5.2 .. \ && cmake --build . --target install \ && cp -r /usr/local/rabbitmq-c-0.5.2/lib64 /usr/local/rabbitmq-c-0.5.2/lib \ && cd /service/ops/php-5.5.25/ext \ && wget http://pecl.php.net/get/amqp-1.4.0.tgz \ && tar -zxvf amqp-1.4.0.tgz && cd amqp-1.4.0 \ && phpize \ && ./configure --with-php-config=/service/app/php-5.5.25/bin/php-config --with-amqp --with-librabbitmq-dir=/usr/local/rabbitmq-c-0.5.2 \ && make && make install \ && echo "extension=amqp.so" &>> /service/app/php-5.5.25/etc/scan.d/amqp.ini
RUN cd /service/ops/php-5.5.25/ext \ && wget http://pecl.php.net/get/memcache-2.2.5.tgz \ && tar -zxvf memcache-2.2.5.tgz && cd memcache-2.2.5 \ && phpize \ && ./configure --with-php-config=/service/app/php-5.5.25/bin/php-config \ && make && make install \ && echo "extension=memcache.so" &>> /service/app/php-5.5.25/etc/scan.d/memcache.ini
RUN yum install -y libmemcached libmemcached-devel \ && cd /service/ops/php-5.5.25/ext \ && wget http://pecl.php.net/get/memcached-2.2.0.tgz \ && tar -zxvf memcached-2.2.0.tgz && cd memcached-2.2.0 \ && phpize \ && ./configure --with-php-config=/service/app/php-5.5.25/bin/php-config \ && make && make install \ && echo "extension=memcached.so" &>> /service/app/php-5.5.25/etc/scan.d/memcached.ini
RUN cd /service/ops/php-5.5.25/ext \ && wget http://pecl.php.net/get/mongo-1.6.10.tgz \ && tar -zxvf mongo-1.6.10.tgz && cd mongo-1.6.10 \ && phpize \ && ./configure --with-php-config=/service/app/php-5.5.25/bin/php-config \ && make && make install \ && echo "extension=mongo.so" &>> /service/app/php-5.5.25/etc/scan.d/mongo.ini
RUN cd /service/ops/php-5.5.25/ext \ && wget http://pecl.php.net/get/mongodb-1.1.6.tgz \ && tar -zxvf mongodb-1.1.6.tgz && cd mongodb-1.1.6 \ && phpize \ && ./configure --with-php-config=/service/app/php-5.5.25/bin/php-config \ && make && make install \ && echo "extension=mongodb.so" &>> /service/app/php-5.5.25/etc/scan.d/mongodb.ini
RUN yum install -y librdkafka librdkafka-devel \ && cd /service/ops/php-5.5.25/ext \ && wget http://pecl.php.net/get/rdkafka-3.0.5.tgz \ && tar -zxvf rdkafka-3.0.5.tgz && cd rdkafka-3.0.5 \ && phpize \ && ./configure --with-php-config=/service/app/php-5.5.25/bin/php-config \ && make && make install \ && echo "extension=rdkafka.so" &>> /service/app/php-5.5.25/etc/scan.d/rdkafka.ini
RUN cd /service/ops/php-5.5.25/ext \ && wget http://pecl.php.net/get/redis-2.2.8.tgz \ && tar -zxvf redis-2.2.8.tgz && cd redis-2.2.8 \ && phpize \ && ./configure --with-php-config=/service/app/php-5.5.25/bin/php-config \ && make && make install \ && echo "extension=redis.so" &>> /service/app/php-5.5.25/etc/scan.d/redis.ini
RUN yum install -y libuuid libuuid-devel \ && cd /service/ops/php-5.5.25/ext \ && wget http://pecl.php.net/get/uuid-1.0.4.tgz \ && tar -zxvf uuid-1.0.4.tgz && cd uuid-1.0.4 \ && phpize \ && ./configure --with-php-config=/service/app/php-5.5.25/bin/php-config \ && make && make install \ && echo "extension=uuid.so" &>> /service/app/php-5.5.25/etc/scan.d/uuid.ini
RUN cd /service/ops/php-5.5.25/ext/mysql \ && phpize \ && ./configure --with-php-config=/service/app/php-5.5.25/bin/php-config --with-pdo-mysql=mysqlnd \ && make && make install \ && echo "extension=mysql.so" &>> /service/app/php-5.5.25/etc/scan.d/mysql.ini
COPY ./files/qbus.so /service/app/php-5.5.25/lib/php/extensions/no-debug-non-zts-20121212/qbus.so COPY ./files/qcm.so /service/app/php-5.5.25/lib/php/extensions/no-debug-non-zts-20121212/qcm.so COPY ./files/qconf.so /service/app/php-5.5.25/lib/php/extensions/no-debug-non-zts-20121212/qconf.so COPY ./files/qlogphp.so /service/app/php-5.5.25/lib/php/extensions/no-debug-non-zts-20121212/qlogphp.so COPY ./files/mobilekill_protobuf.so /service/app/php-5.5.25/lib/php/extensions/no-debug-non-zts-20121212/mobilekill_protobuf.so COPY ./files/lib64/libqlogThread.so /usr/lib64/libqlogThread.so COPY ./files/lib64/liblog4cplus-1.1.so.7 /usr/lib64/liblog4cplus-1.1.so.7 COPY ./files/lib64/libprotobuf.so.7 /usr/lib64/libprotobuf.so.7
RUN echo "extension=qbus.so" &>> /service/app/php-5.5.25/etc/scan.d/qbus.ini \ && echo "extension=qcm.so" &>> /service/app/php-5.5.25/etc/scan.d/qcm.ini \ && echo "extension=qconf.so" &>> /service/app/php-5.5.25/etc/scan.d/qconf.ini \ && echo "extension=qlogphp.so" &>> /service/app/php-5.5.25/etc/scan.d/qlogphp.ini \ && echo "extension=mobilekill_protobuf.so" &>> /service/app/php-5.5.25/etc/scan.d/mobilekill_protobuf.ini \ && curl -sS https://getcomposer.org/installer | php \ && mv composer.phar /usr/bin/composer
RUN mkdir -p /service/app/nginx-1.16.1 \ && mkdir -p /service/ops && cd /service/ops \ && wget http://nginx.org/download/nginx-1.16.1.tar.gz \ && tar -zxvf nginx-1.16.1.tar.gz \ && cd nginx-1.16.1 \ && ./configure --user=root --group=root --prefix=/service/app/nginx-1.16.1/ \ --pid-path=/service/app/nginx-1.16.1/run/nginx.pid --lock-path=/service/app/nginx-1.16.1/run/nginx.lock \ --with-http_stub_status_module --with-http_ssl_module --with-stream --with-http_gzip_static_module --with-http_sub_module \ && make && make install
ENV PATH=$PATH:/service/app/nginx-1.16.1/sbin
RUN yum install -y openssh-server net-tools vim \ && echo 'root:4mCrjpI2si7hVJLPv6zMe' | chpasswd \ && sed -i 's/#Port 22/Port 15201/' /etc/ssh/sshd_config \ && sed -i 's/PermitRootLogin no/PermitRootLogin yes/' /etc/ssh/sshd_config \ && sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config \ && ssh-keygen -A \ && mkdir -p /root/.ssh \ && echo "ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxxxx" >> /root/.ssh/authorized_keys
RUN mkdir /service/run
COPY ./config/nginx.conf /service/app/nginx-1.16.1/conf/nginx.conf COPY ./config/php.ini /service/app/php-5.5.25/etc/php.ini COPY ./config/php-fpm.conf /service/app/php-5.5.25/etc/php-fpm.conf COPY ./run.sh /service/run.sh
EXPOSE 15201-15400
VOLUME "/service/www" VOLUME "/service/conf" VOLUME "/service/logs" VOLUME "/service/data"
WORKDIR "/service/data"
RUN chmod 777 -R /service/run.sh \ && echo "export PATH=$PATH" >> /root/.bashrc
CMD ["/service/run.sh"]
|
开发环境:NODEJS-14.9.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| FROM harbor.qihoo.net/ybjcjdz-android-platform/centos:7_v1
RUN rm -rf /etc/localtime \ && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && yum install -y wget make cmake gcc gcc-c++ autoconf libxml2 libxml2-devel openssl openssl-devel bzip2 bzip2-devel libcurl libcurl-devel libxslt libxslt-devel git \ && mkdir -p /service/www \ && mkdir -p /service/app \ && cd /service/app \ && wget https://nodejs.org/dist/v14.9.0/node-v14.9.0-linux-x64.tar.gz \ && tar -zxvf node-v14.9.0-linux-x64.tar.gz
ENV PATH=$PATH:/service/app/node-v14.9.0-linux-x64/bin/
RUN git config --global url."https://".insteadOf git:// \ && npm config set registry https://registry.npmmirror.com/
RUN yum install -y openssh-server net-tools vim \ && echo 'root:4mCrjpI2si7hVJLPv6zMe' | chpasswd \ && sed -i 's/#Port 22/Port 15801/' /etc/ssh/sshd_config \ && sed -i 's/PermitRootLogin no/PermitRootLogin yes/' /etc/ssh/sshd_config \ && sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config \ && ssh-keygen -A \ && mkdir -p /root/.ssh \ && echo "ssh-rsa xxxxxxxxxxxxxxxxxx" >> /root/.ssh/authorized_keys
COPY ./run.sh /service/run.sh
EXPOSE 15801-16000
VOLUME "/service/www" VOLUME "/service/conf" VOLUME "/service/logs" VOLUME "/service/data"
WORKDIR "/service/data"
RUN chmod 777 -R /service/run.sh \ && echo "export PATH=$PATH" >> /root/.bashrc
CMD ["/service/run.sh"]
|
开发环境:GO-1.16.15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| FROM harbor.qihoo.net/ybjcjdz-mobile-url-collection/centos:7_v1
LABEL maintainer="cuixiaogang@360.cn"
RUN yum install -y openssl openssl-devel libcurl libcurl-devel libmcrypt libmcrypt-devel zlib zlib-devel libxml2 libxml2-devel curl boost-devel bzip2 bzip2-devel libcurl libcurl-devel libxslt libxslt-devel pcre pcre-devel wget make gcc gcc-c++ cmake autoconf vim net-tools git \ && rm -rf /var/cache/yum \ && yum clean all \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && mkdir -p /service/app \ && cd /service/app \ && wget https://go.dev/dl/go1.16.15.linux-amd64.tar.gz \ && tar -zxvf go1.16.15.linux-amd64.tar.gz \ && mv go go-1.16.15
ENV PATH=$PATH:/service/app/go-1.16.15/bin
RUN go env -w GOPATH=/service/data/library/go_path \ && go env -w GOCACHE=/service/data/library/go_cache \ && go env -w GONOPROXY="w.src.corp.qihoo.net" \ && go env -w GONOSUMDB="w.src.corp.qihoo.net" \ && go env -w GO111MODULE=on \ && git config --global url."https://".insteadOf git:// \ && git config --global url."ssh://git@code.geelib.qihoo.net:10022/whitelist_scancenter/go-biz-library.git".insteadOf "https://w.src.corp.qihoo.net/android-analysis/go-biz-library.git" \ && git config --global url."ssh://git@code.geelib.qihoo.net:10022/whitelist_scancenter/go-library.git".insteadOf "https://w.src.corp.qihoo.net/android-analysis/go-library.git"
RUN yum install -y openssh-server \ && echo 'root:4mCrjpI2si7hVJLPv6zMe' | chpasswd \ && sed -i 's/#Port 22/Port 15401/' /etc/ssh/sshd_config \ && sed -i 's/PermitRootLogin no/PermitRootLogin yes/' /etc/ssh/sshd_config \ && sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config \ && ssh-keygen -A \ && mkdir -p /root/.ssh \ && echo "ssh-rsa xxxxxxxxx" >> /root/.ssh/authorized_keys
RUN mkdir /service/run
COPY ./run.sh /service/run.sh
EXPOSE 15401-15600
VOLUME "/service/www" VOLUME "/service/conf" VOLUME "/service/logs" VOLUME "/service/data"
WORKDIR "/service/data"
RUN chmod 777 -R /service/run.sh \ && echo "export PATH=$PATH:/service/data/library/go_path/bin" >> /root/.bashrc
CMD ["/service/run.sh"]
|
构建命令
1 2 3 4 5 6 7 8
| # 示例中的构建命令 docker build -t cxg_dev_go_1_16_15:1.0 -f ./Dockerfile .
# 指定其他Dockerfile docker build -t myapp:1.0 -f Dockerfile.prod .
# 传入构建参数 docker build --build-arg VERSION=2.0 -t myapp .
|
末尾的.是「构建上下文」,Docker会把这个目录打包发给daemon,所以别在体积巨大的目录里直接build,必要时用.dockerignore排除无关文件。
镜像与 Registry
镜像是Docker分发的核心载体。前面「Dockerfile」讲了怎么把镜像构建出来,这一章把剩下的部分收口:镜像到底由什么组成(分层),以及怎么在机器之间分发(build/tag/push/pull 和私有仓库)。
镜像分层
一个镜像不是单一文件,而是由多个只读层(layer)自下而上叠起来的。每条Dockerfile指令(主要是RUN/COPY/ADD)生成一层,Docker用联合文件系统(如overlay2)把这些层叠成一个完整的文件视图。
镜像分层
分层带来两个直接好处:
- 缓存复用:构建时某一层没变就直接用缓存,所以改一行代码重新build往往很快,只有改动的那层及其之后的层会重建。
- 存储共享:多个镜像若基于同一个
FROM基础镜像,这些底层在磁盘上只存一份,省空间。
容器运行时会在镜像最上面再加一个可写层,所有改动都写在这一层(写时复制),不影响下面只读的镜像层——这也是删掉容器、镜像还在的原因。
可以使用命令docker history [images]{:tag}来查看分层信息
一个常用的镜像分层
镜像命名与标签
镜像的完整名字格式:
1 2
| [仓库地址[:端口]/]仓库名[:标签] 例:registry.example.com:5000/team/app:1.2
|
- 不写仓库地址,默认是Docker Hub(
docker.io)。
- 不写标签,默认
latest。注意latest只是个默认名字,并不保证是最新版本。
- 每个镜像还有唯一的内容摘要
sha256:...,可用镜像名@sha256:...精确锁定某个版本。
镜像处理全流程
把自己的镜像推到仓库、再在别的机器拉下来,是最典型的分发流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| docker build -t app:1.0 .
docker tag app:1.0 registry.example.com/team/app:1.0
docker login registry.example.com
docker push registry.example.com/team/app:1.0
docker pull registry.example.com/team/app:1.0 docker run -d registry.example.com/team/app:1.0
|
关键是第2步tag:push推到哪里,是由镜像名里的仓库地址决定的,所以推送前必须先把镜像重命名成仓库地址/项目/名字的形式。
如果不方便联网推送,也可以用docker save把镜像打成tar包,拷过去再docker load:
1 2
| docker save -o app.tar app:1.0 # 导出镜像为tar docker load -i app.tar # 在另一台机器导入
|
注意区分:save/load针对的是镜像,export/import针对的是容器的文件系统(会丢掉分层和元数据),日常分发镜像用前者。
私有仓库与 Harbor
Docker Hub是公共仓库,公司内部一般要自建私有仓库,常见两种:
| 方案 |
说明 |
registry:2 |
Docker官方的Distribution,轻量,只有存储和HTTP API,没有界面和权限管理 |
| Harbor |
CNCF项目,在registry之上补齐了一整套企业能力 |
Harbor是目前企业里最常用的私有镜像仓库,相比裸registry,它主要补齐了:
- 图形界面 + 项目管理:按项目(project)组织镜像,可分公开/私有。
- 权限控制(RBAC):用户、角色、项目级别的访问权限。
- 漏洞扫描:集成Trivy等扫描器,push上来的镜像自动扫CVE。
- 镜像签名:保证镜像来源可信、未被篡改。
- 复制(replication):多个Harbor之间、或与Docker Hub之间同步镜像,做多机房分发。
用法上和普通仓库没区别:docker login harbor.example.com之后,push/pull照常,把地址换成Harbor的域名和项目名即可。
小结
| 操作 |
命令 |
| 构建 |
docker build -t 名:tag . |
| 重命名 |
docker tag 旧名 仓库/项目/名:tag |
| 登录 |
docker login 仓库地址 |
| 推送 |
docker push 仓库/项目/名:tag |
| 拉取 |
docker pull 仓库/项目/名:tag |
| 离线导出/导入 |
docker save -o x.tar 名 / docker load -i x.tar |
Docker Compose
当一个应用由多个容器组成(比如web加数据库加缓存),一条条敲docker run既繁琐又难维护。Docker Compose用一个docker-compose.yml(新版也叫compose.yml)文件,把多个服务、网络、卷一次性声明清楚,一条命令拉起整套。
命令有两代:老的docker-compose(v1,Python写的,带连字符)和新的docker compose(v2,Go写的Docker插件,无连字符)。新版已是主流,下面统一用docker compose。
文件结构(顶层关键字)
| 顶层关键字 |
作用 |
version |
Compose文件版本,Compose Spec之后已可省略,旧文件里常见 |
services |
核心,定义各个服务(每个服务对应一类容器) |
networks |
声明自定义网络 |
volumes |
声明命名卷 |
configs |
配置项,主要用于Swarm |
secrets |
敏感信息,主要用于Swarm |
services 下常用关键字
| 关键字 |
作用 |
对应docker run |
image |
使用的镜像 |
镜像名 |
build |
用Dockerfile现场构建,可指定上下文和文件 |
docker build |
command |
覆盖默认启动命令 |
CMD |
entrypoint |
覆盖入口 |
--entrypoint |
container_name |
指定容器名 |
--name |
ports |
端口映射宿主:容器 |
-p |
expose |
仅声明端口,不映射到宿主 |
EXPOSE |
environment |
设置环境变量 |
-e |
env_file |
从文件读环境变量 |
--env-file |
volumes |
挂载卷或目录 |
-v |
networks |
加入的网络 |
--network |
depends_on |
声明服务启动的先后依赖 |
- |
restart |
重启策略 |
--restart |
healthcheck |
健康检查 |
HEALTHCHECK |
deploy |
部署配置(副本数、资源限制,主要Swarm) |
- |
labels |
元数据标签 |
--label |
working_dir |
工作目录 |
-w |
user |
运行用户 |
-u |
一个完整示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| version: '3.7'
services: transfer: image: "harbor.qihoo.net/ybjcjdz-downloader/mobile-url-downloader:20260318100535" command: [ "downloadTransfer" ] deploy: mode: replicated replicas: 5 networks: - net mobile_url_collection: image: "harbor.qihoo.net/ybjcjdz-downloader/mobile-url-downloader:20260318100535" command: [ "mobileUrlCollectionDownloader" ] deploy: mode: replicated replicas: 512 resources: reservations: memory: 2G update_config: parallelism: 8 delay: 3s failure_action: pause order: stop-first networks: - net cooperator: image: "harbor.qihoo.net/ybjcjdz-downloader/mobile-url-downloader:20260318100535" command: [ "cooperatorDownloader" ] deploy: mode: replicated replicas: 0 networks: - net spider: image: "harbor.qihoo.net/ybjcjdz-downloader/mobile-url-downloader:20260318100535" command: [ "spiderDownloader" ] deploy: mode: replicated replicas: 0 networks: - net networks: net: driver: overlay ipam: driver: default config: - subnet: "10.1.0.0/16"
|
这是一个实际的下载器服务栈,有几个值得注意的点:
- 一份镜像,多个角色:四个服务(transfer、mobile_url_collection、cooperator、spider)用的是同一个镜像,靠不同的
command跑出不同的进程入口。这是worker型服务常见的组织方式,省去为每个角色单独打镜像。镜像地址harbor.qihoo.net/...正是前面「镜像与 Registry」讲的Harbor私有仓库。
- 副本数(replicas):
deploy.replicas指定每个服务起几个实例——transfer起5个、collection起512个,cooperator和spider先设0(占位,需要时再scale起来)。
- 滚动更新控制:collection因为副本多,额外配了
update_config:parallelism: 8每批只启动/更新8个、delay控制批次间隔、order: stop-first先停旧的再起新的、failure_action: pause出错就暂停。一次性拉起512个容器会把网络和iptables压垮,分批正是为此。它还用resources.reservations.memory预留了内存。
- overlay 网络:所有服务都接在名为
net的overlay网络上。overlay是跨主机的网络(前面「网络」一章提过),Swarm集群里多台机器上的容器互通就靠它,这里还指定了子网10.1.0.0/16。
要特别说明:这份文件用到了deploy,本质是Swarm的stack文件,得用docker stack deploy -c xxx.yml 栈名部署到Swarm集群才完整生效。普通的docker compose up只认其中一部分字段(如replicas、resources),update_config、mode这些会被直接忽略。
常用命令
| 命令 |
作用 |
docker compose up -d |
后台拉起所有服务,不存在则创建 |
docker compose down |
停止并删除容器、网络(加-v连卷一起删) |
docker compose ps |
查看本项目的容器状态 |
docker compose logs -f |
跟踪查看日志 |
docker compose build |
构建或重新构建镜像 |
docker compose exec web bash |
进入某个服务的容器 |
docker compose restart |
重启服务 |
docker compose stop / start |
停止/启动服务但不删除 |
up时Compose会自动建一个项目专属的网络,所有服务默认都接在上面,因此服务之间天然能用服务名互访,这和前面「网络」一章的自定义bridge是一脉相承的。