Docker-核心笔记(含Dockerfile,Compose)
Docker-核心笔记(含Dockerfile,Compose)
2017/03 Chenxin
参考
https://yeasy.gitbooks.io/docker_practice Docker入门与实践 电子书,截止201807一直在更新.
http://guide.daocloud.io/dcs/daocloud-services-9152632.html Docker加速器
https://blog.csdn.net/S_gy_Zetrov/article/details/78161154 入门
http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html
http://www.ruanyifeng.com/blog/2018/02/docker-wordpress-tutorial.html
命令自动补全(不属于docker内容)
安装(仅支持较新的linux版本)
yum install bash-completion
说明(可以忽略)
最小化安装centos7需要安装一个 bash-completion 包,然后退出 bash,重新登录即可.
bash-2.05及以后的版本提供了自动补齐的编程接口.
bash-completion的安装已加入Centos7初始化脚本.
常用指令汇总
yum install docker # 安装
docker version
docker info
docker search centos
docker pull centos:latest
docker image ls --all
docker image rmi image_id # 删除
docker image save image_id -o centosvim.tar # 输出一个tar格式的
docker image load -i centosvim.tar # 载入一个tar格式的
docker image tag image_id centosvim:1.0
docker image inspect image_id #获取镜像元数据
docker container ls --all
docker container attach container_id # ctrl+p, ctrl+q 将容器推到后台后,推荐使用exec方式进入,防止退出的时候错误的将容器终止了(Ctrl+d)
docker container exec -it container_id /bin/bash # 推荐
docker container exec -it --user root container_id /bin/bash #登陆容器,并成为root
docker container run -it --name container_name image_id /bin/bash #启动一个容器
docker container run -d -p 1022:80 foo/live /bin/bash #物理机的1022映射到docker的80;
docker container run -d -p 127.0.0.2:8080:80 --rm --name wordpress --env WORDPRESS_DB_PASSWORD=123456 --link wordpressdb:mysql --volume "$PWD/wordpress":/var/www/html wordpress
docker container run -p 1022:80 -it centos:lastest /bin/bash
docker container port container_id
docker container inspect container_id #获取容器元数据
docker container cp container_id:[/path/to/file] /local/path/dir/
docker container commit -m "centos with vim" -a "chenxin" container_id chenxin/centos:vim
docker container rm container_id
docker container prune #删除所有处于stop状态的container
docker container stats container_id #显示当前运行的这个容器资源占用情况(CPU/MEM/NET/IO/PIDS)
docker container top container_id #显示容器中当前运行的pid信息
docker container start container_id
docker container stop container_id
ctrl+p ctrl+q # 容器正常运行,用户临时退出bash
exit / ctrl+d # 用户退出,可能导致容器stop
docker diff container_id # 查看容器存储层改动记录
docker history image_id # 查看image内的历史记录(每层改动情况)
docker build -t xbzj . # 使用Dockerfile构建镜像
用户权限
普通用户执行docker指令需要的权限
xbzj账号执行 docker search java
报错Got permission denied while trying to connect to the Docker daemon socket at ...permission denied
将xbzj加入到docker用户组就可以了: usermod -G docker xbzj
安装部署/目录说明/启停方式
网络说明
因大陆访问海外download.docker.com存在网络问题.可以使用rpm方式安装.之后可以使用大陆第三方提供的Docker库(比如阿里云,或daocloud)
AWS上本身有docker库,可以直接yum安装.如果增加yum源安装官网版本,需要解决一些依赖问题.
建议采取yum方式安装(AWS默认的yum源)
docker分为社区版(ce)和企业版(ee收费)
官方安装文档 https://docs.docker.com/install/linux/docker-ce/centos/#uninstall-old-versions
yum安装方式
a.配置yum仓库(aws不需此步骤)
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
ls /etc/yum.repos.d/
amzn-main.repo docker-ce.repo
b.正式安装
yum install docker-ce
这里会报依赖错误.可以自行解决.或使用AWS自身的yum源(推荐)
yum install docker (aws只需要这一步就可以了)
rpm安装方式
https://download.docker.com/linux/centos/7/x86_64/stable/Packages/
https://download.docker.com/
rpm安装完成后,执行rpm -ql docker
/etc/rc.d/init.d/docker #启动关停状态查看脚本
/etc/sysconfig/docker #配置文件,可以配置docker的一些目录信息和日志信息
/etc/sysconfig/docker-storage #文件配置
/etc/udev/rules.d/80-docker.rules
/usr/bin/docker
/usr/bin/docker-containerd
/usr/bin/docker-containerd-shim
/usr/bin/docker-ctr
/usr/bin/docker-init
/usr/bin/docker-proxy
/usr/bin/docker-runc
/usr/bin/dockerd
/usr/share/bash-completion/docker
/usr/share/vim/vimfiles/syntax/dockerfile.vim
/var/lib/docker #主要目录
安装完成后的验证
执行docker version
Client:
Version: 18.03.1-ce #这个是aws自己的版本,是18年03月做的AMI.
docker主要目录说明
ls /var/lib/docker/
builder containerd containers image network overlay2 plugins runtimes swarm tmp trust volumes
/var/lib/docker/devicemapper/devicemapper/data #用来存储相关的存储池数据
/var/lib/docker/devicemapper/devicemapper/metadata #用来存储相关的元数据。
/var/lib/docker/devicemapper/metadata/ #用来存储 device_id、大小、以及传输_id、初始化信息
/var/lib/docker/devicemapper/mnt #用来存储挂载信息
/var/lib/docker/container/ #用来存储容器信息
/var/lib/docker/graph/ #用来存储镜像中间件及本身详细信息和大小 、以及依赖信息
/var/lib/docker/repositores-devicemapper #用来存储镜像基本信息
/var/lib/docker/tmp #docker临时目录
/var/lib/docker/trust #docker信任目录
/var/lib/docker/volumes #docker卷目录
docker启停
service docker start 或者 systemctl start docker
镜像与源站说明
查询镜像
docker search centos
拉取
docker pull centos
或者
docker pull centos:latest
会默认放到/var/lib/docker/overlay2/目录,进入查看如下:
ls /var/lib/docker/overlay2/00...28/diff
bin dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
目录和文件内容跟我们安装系统后的相同(连日志都有了).
镜像源地址
a.查看镜像源地址
docker info
b.修改镜像源地址(2种方式)
1.增加或修改/etc/docker/daemon.json (18.03版本后就没有这个文件了aws的centos18.03版本.只能新增.1.13版本有这个文件centos7yum安装)
{
"registry-mirrors": ["http://hub-mirror.c.163.com"]
}
2.修改或新增 /etc/sysconfig/docker,在OPTIONS变量后追加参数
OPTIONS="--default-ulimit nofile=1024:4096 --registry-mirror=https://docker.mirrors.ustc.edu.cn"
修改完成后,重启docker.经过测试,以上2个地址都可用.
Docker国内源
a.国内源
Docker 官方中国区 https://registry.docker-cn.com
中国科技大学 https://docker.mirrors.ustc.edu.cn
网易 http://hub-mirror.c.163.com
阿里云 https://pee6w651.mirror.aliyuncs.com
默认docker源: Registry: https://index.docker.io/v1/
b.加速器
需要注册的加速器使用说明:
登陆阿里云docker的registry:(需要先注册阿里云账号,在容器镜像服务里配置registry密码.将生成的镜像url复制到docker配置文件里,类似:https://2r6ncmdxli.mirror.aliyuncs.com 的地址.
docker login --username=chenxin6676 registry.cn-shanghai.aliyuncs.com
之后在本地可以push镜像到阿里云.
查看docker镜像版本
查看docker镜像的版本号TAG,从远程仓库拉取自己想要版本的镜像.需要在docker hub查看
地址如下:https://hub.docker.com/r/library/
进入之后,在页面左上角搜索框搜索.
点击 详情->TAG.
docker pull java:latest
docker指令使用说明
1 查看本地镜像
docker image ls --all
2 容器启动参数
docker container run -it --name test image_id /bin/bash
具有自动抓取 image 的功能.如果发现本地没有指定的 image 文件,就会从仓库自动抓取.
-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
-i 则让容器的标准输入保持打开
--name test:容器的名字叫做test.
docker container run --rm --name wordpress --volume "$PWD/":/var/www/html php:5.6-apache
--rm:停止运行后,自动删除容器文件.
--volume "$PWD/":/var/www/html:将宿主机当前目录($PWD变量内容)映射到容器的/var/www/html.
php:5.6-apache : 来源镜像
docker container run -d --rm --name wordpressdb --env MYSQL_ROOT_PASSWORD=123456 --env MYSQL_DATABASE=wordpress mysql:5.7
-d:容器启动后,在后台运行.
--env MYSQL_ROOT_PASSWORD=123456:向容器进程传入一个环境变量MYSQL_ROOT_PASSWORD,该变量会被用作 MySQL 的根密码.
--env MYSQL_DATABASE=wordpress:向容器进程传入一个环境变量MYSQL_DATABASE,容器里面的MySQL会根据该变量创建一个数据库wordpress
mysql:5.7 : 来源镜像
docker container run -d -p 127.0.0.2:8080:80 --rm --name wordpress --env WORDPRESS_DB_PASSWORD=123456 --link wordpressdb:mysql --volume "$PWD/wordpress":/var/www/html wordpress
-p 127.0.0.2:8080:80:物理机127.0.0.2的8080映射到器的 80 端口.
--link wordpressdb:mysql,表示 wordpress 容器要连到(先前创建的)wordpressdb容器,冒号表示该容器的别名是mysql.
浏览器访问物理机127.0.0.2:8080就能看到 WordPress 的安装提示了.而且,你在wordpress子目录下的每次修改,都会反映到容器里面.
最后,终止上面这两个容器(--rm,容器文件会自动删除): docker container stop wordpress wordpressdb
docker container run -d -p 10.0.0.10:8080:80 --volume /home/admin/www/:/usr/local/apache2/htdocs --name apache httpd 或者
docker container run -d -p 8080:80 --volume ...xxx...
以上使用一个httpd的镜像创建一个apache的容器
docker启动容器的时候,可以指定容器的IP地址,但需要额外配置网络模式,具体请参考网上资料https://blog.csdn.net/sbxwy/article/details/78962809 .
3 detach容器
如果想让容器一直运行,而不是停止,可以使用快捷键 ctrl+p ctrl+q 退出,此时容器的状态为Up.
4 attach进入到这个容器
exec方式进入(run的时候没有指定 -it /bin/bash的)容器
docker container exec -it [containerID] /bin/bash
如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令进入容器.一旦进入了容器,就可以在容器的 Shell 执行命令了.
docker container attach container_id # 不推荐此方式.推荐exec方式,因为attach完,直接ctrl+d,容器状态会变成exited.而exec不会.
5 终止容器(多种方式)
在容器内,使用 exit,命令退出,则容器的状态处于Exit,而不是后台运行.
或者 docker container stop [containID]
或者 docker container kill [containID]
6 查看当前运行的容器/已停止的容器
docker container ls 同 docker ps (查看正在run中的容器)
docker container ls --all 同 docker ps -a 查看所有容器,包括停止的(Exited状态)
docker container rm `docker container ls -aq` 只列出docker container的id,并删除掉所有container.
7 启动、停止、重启容器
docker container start ca5b2982c795 #[containID]
docker container stop ca5b2982c795
docker container restart ca5b2982c795
docker stop $(docker ps -aq) 停止所有的容器
8 与宿主机间对拷文件
docker container cp [containID]:[/path/to/file] /local/path/dir/ # 拷贝容器内文件到物理机上
9 commit指令
commit利用container创建image.
应用场合,比如被入侵后保存现场等。但是,不要使用 docker commit 定制镜像,定制镜像应该使用 Dockerfile 来完成。
docker container commit -m "centos with vim" -a "chenxin" container_id chenxin/centos:vim
-m指定说明信息;
-a指定用户信息;
ca5b2982c795代表容器的id;
chenxin/centos:vim指定目标镜像的用户名、仓库名和 tag 信息.
查看我们刚刚创建镜像对应生成的文件
/var/lib/docker/image/overlay2/imagedb/metadata/sha256/38...348/ 这里是lastUpdated和parent的2个文本文件说明(起源于哪个镜像).
/var/lib/docker/image/overlay2/imagedb/content/sha256/38...348 这个文本文件详细说明了这个镜像的配置信息
10 删除 容器/镜像
删除单个容器 docker container rm [containerID] (非运行状态的才允许删除,否则请先docker stop/kill)
删除全部容器 docker container rm `docker container ls -aq` 或 docker container rm `docker ps -aq`
删除单个镜像 docker image rmi image_id
删除所有镜像 docker image rmi `docker image ls -aq`
删除所有在 mongo:3.2 之前的镜像:docker image rmi $(docker image ls -q -f before=mongo:3.2) # -f为filter.
删除一个运行中的容器,可以添加 -f 参数 docker container rm -f myweb
删除所有处于终止状态的容器 docker container prune
docker rm : 删除一个或多个 容器
docker rmi : 删除一个或多个 镜像
docker prune: 用来删除不再使用的 docker 对象
11 将image导出后再导入其他装了docker的机器
docker image save image_id -o centosvim.tar #如何将自己的image硬拷贝
docker image load -i centosvim.tar #将tar文件拷贝到其他机器后,执行导入image
docker image tag 38f309423638 centosvim:1.0 #上面的REPOSITORY/TAG都是none,那么可以tag
12 push自己的image到第三方仓库(aws)
a.将自己的image给push到docker官方的hub
这里需要到官方注册账号才行.需要先登录 docker login;然后 docker push chenxin/centos:vim .这样以后可以通过自己的账号可以从官网pull下来自己的image.
b.将image给push到aws的ECR(ERS)
docker build -t xbzj . # 首先本地构建镜像,以xbzj为例
aws ecr get-login --no-include-email --region ap-southeast-1 #执行awscli指令获取登陆ECR命令
docker login -u AWS -p eyJwY...M30= https://61..9551.dkr.ecr.a..t-1.amazonaws.com #根据上条命令输出登陆token,登陆完成.
docker tag xbzj:latest 61..51.dkr.ecr.ap-southeast-1.amazonaws.com/xbzj:latest #对image打TAG
docker push 615..51.dkr.ecr.ap-southeast-1.amazonaws.com/xbzj:latest #将本地image给PUSH到ECR
13 端口映射NAT
docker启动服务,开启端口,如何映像到外部的IP和端口,从而对外提供服务(2种方式).
容器中启动的服务,可以通过宿主机直接访问(route物理机会看到172.17.0.0的路由docker0),但外部机器无法访问,那么就需要将docker端口映射到宿主机端口上.
查看物理机路由表,执行
# route
Destination Gateway Genmask Flags Metric Ref Use Iface
172.17.0.0 * 255.255.0.0 U 0 0 0 docker0
2种方式,如下:
1.通过容器指令(推荐)
docker container run -d -p 1022:80 foo/live /bin/bash #物理机的1022映射到docker的80;
docker container run -p 1022:80 -it centos:lastest /bin/bash #将物理机的1022端口映射docker的80端口
docker container run -d -p 10.0.0.10:1022:80 --volume /home/admin/www/:/usr/local/apache2/htdocs --name apache httpd # 指定了映射到哪个物理网卡
docker container run -d -P image_id #这里的大写P,会将物理机随机1个端口映射到Dockfile文件EXPOSE声明的端口上.
2.通过iptables的映射
docker inspect container_id |grep IPAddress ->172.17.0.2
iptables -t nat -A DOCKER -p tcp --dport 8001 -j DNAT --to-destination 172.17.0.2:8000 # 将物理机的8001映射到容器的8000.
进入docker container里,启动httpd服务,并netstat检查:/usr/sbin/httpd
检查物理机端口开放:
tcp 0 0 :::8000 :::* LISTEN 14066/docker-proxy
14 容器的 volume 子命令 (或 -v)
docker专门提供了volume子命令来操作数据卷:
create 创建数据卷
inspect 显示数据卷的详细信息
ls 列出所有的数据卷
prune 删除所有未使用的 volumes,并且有 -f 选项
rm 删除一个或多个未使用的 volumes,并且有 -f 选项
15 关于容器和镜像的挂载卷说明
一.通过docker run命令.在docker run后的 -v 指令(只对创建的容器有效)
1、运行命令:docker run --name test -it -v /home/xqh/myimage:/data ubuntu /bin/bash -> 主机上的 /home/xqh/myimage 目录中的内容关联到 容器中设置了一个挂载点 /data.在容器还是主机上操作它,都是完全实时同步的(实际指向的物理空间完全相同).
2、运行命令:docker run --name test1 -it -v /data ubuntu /bin/bash ->docker会自动绑定主机上的一个目录到容器的/data上。(其目的不是让在主机上修改,而是让多个容器共享。)
二.通过dockerfile创建镜像声明的挂载点.
通过dockerfile的 VOLUME 方式对镜像有效.(在镜像中创建挂载点).与容器-v参数相比,有一个区别是,通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,是自动生成的。
FROM ubuntu
MAINTAINER hello1
VOLUME ["/data1","/data2"]
上面的dockfile文件通过VOLUME指令指定了两个挂载点 /data1 和 /data2. 如果用此镜像创建容器,则容器中的/data1和/data2是随机挂载的物理主机上的目录.
三.容器共享卷(挂载点) --volumes-from (会将原容器的dockerfile中声明的数据卷共享到自己这个容器上来,以便保持这个目录与原容器的一致)
docker run --name test1 -it myimage /bin/bash #myimage是用前面的dockerfile文件构建的镜像。 这样容器test1就有了 /data1 和 /data2两个挂载点。
下面我们创建其他容器可以和test1共享 /data1 和 /data2卷 ,在 docker run中使用 --volumes-from标记,如:
来源不同镜像,如:docker run --name test2 -it --volumes-from test1 ubuntu /bin/bash
来源相同镜像,如:docker run --name test3 -it --volumes-from test1 myimage /bin/bash
上面的三个容器 test1 , test2 , test3 均有 /data1 和 /data2 两个目录,且目录中内容是共享的,任何一个容器修改了内容,别的容器都能获取到。
四.最佳实践:数据容器
如果多个容器需要共享数据(如持久化数据库、配置文件或者数据文件等),可以考虑创建一个特定的数据容器,该容器有1个或多个卷。其它容器通过--volumes-from 来共享这个数据容器的卷。
因为容器的卷本质上对应主机上的目录,所以这个数据容器也不需要启动。如: docker run --name dbdata myimage echo "data container"
说明:有个卷,容器之间的数据共享比较方便,但也有很多问题需要解决,如权限控制、数据的备份、卷的删除等。这些内容以后介绍。
16 对比 mount 挂载数据卷 方式 的说明
之前我们使用 --volume(-v) 选项来挂载数据卷,现在 docker 提供了更强大的 --mount 选项来管理数据卷。mount 选项可以通过逗号分隔的多个键值对一次提供多个配置项,因此 mount 选项可以提供比 volume 选项更详细的配置。使用 mount 选项的常用配置如下:
type 指定挂载方式,我们这里用到的是 volume,其实还可以有 bind 和 tmpfs。
volume-driver 指定挂载数据卷的驱动程序,默认值是 local。
source 指定挂载源,对于一个命名的数据卷,这里应该指定这个数据卷的名称.在使用时可以写source,也可以简写为 src
destination 指定挂载的数据在容器中的路径。在使用时可以写 destination,也可以简写为 dst 或 target。
readonly 指定挂载的数据为只读。
volume-opt 可以指定多次,用来提供更多的 mount 相关的配置。
下面我们看个具体的例子:
$ docker volume create hello
$ docker run -id --mount type=volume,source=hello,target=/world ubuntu /bin/bash
我们创建了名称为 hello 的数据卷,然后把它挂在到容器中的 /world 目录。通过 inspect 命令查看容器的详情中的 "Mounts" 信息可以验证.
使用 volume driver 把数据存储到其它地方
除了默认的把数据卷中的数据存储在宿主机,docker 还允许我们通过指定 volume driver 的方式把数据卷中的数据存储在其它的地方,比如 AWS 的 S3。
简单起见,我们接下来的 demo 演示如何通过 vieux/sshfs 驱动把数据卷的存储在其它的主机上。
docker 默认是不安装 vieux/sshfs 插件的,我们可以通过下面的命令进行安装:
$ docker plugin install --grant-all-permissions vieux/sshfs
然后通过 vieux/sshfs 驱动创建数据卷,并指定远程主机的登录用户名、密码和数据存放目录:
docker volume create --driver vieux/sshfs \
-o sshcmd=nick@10.32.2.134:/home/nick/sshvolume \
-o password=yourpassword \
mysshvolume
注意,请确保你指定的远程主机上的挂载点目录是存在的(demo 中是 /home/nick/sshvolume 目录),否则在启动容器时会报错。
最后在启动容器时指定挂载这个数据卷:
docker run -id \
--name testcon \
--mount type=volume,volume-driver=vieux/sshfs,source=mysshvolume,target=/world \
ubuntu /bin/bash
这就搞定了,你在容器中 /world 目录下操作的文件都存储在远程主机的 /home/nick/sshvolume 目录中。进入容器 testcon 然后在 /world 目录中创建一个文件,然后打开远程主机的 /home/nick/sshvolume 目录进行查看,你新建的文件是不是已经出现在那里了!
17 物理机与运行中的容器 数据的覆盖问题
以物理机器下的卷优先,以有数据的优先.
- 如果挂载一个空的数据卷到容器中的一个非空目录中,那么这个目录下的文件会被复制到数据卷中。
- 如果挂载一个非空的数据卷到容器中的一个目录中,如果容器目录中原先有数据,那么容器中原始数据会被隐藏掉.
这两个规则都非常重要,灵活利用第一个规则可以帮助我们初始化数据卷(物理设备)中的内容。掌握第二个规则可以保证挂载数据卷后的数据总是你期望的结果。
在 Dockerfile 中添加数据卷(镜像里的数据卷声明)(请参考本笔记 "Dockerfile编写" )
18 查看输出和日志(重要,常用)
docker container logs [container-id/names] # 获取容器的输出信息(类似控制台输出等)
docker container logs service-match|tail -n 100
19 控制容器占用系统资源(CPU,内存)
docker create 或者 docker run 的时候
-c|–cpu-shares[=0]参数来调整同期使用CPU的权重.
-m|–memory参数来调整容器使用内存的大小.
如何创建docker的私有仓库 即 Docker Registry 搭建
1.从官方pull一个registery的镜像,通过该镜像与物理机本地文件做好 --volume
2.修改物理机的镜像源站地址
可以参考
https://cloud.tencent.com/developer/article/1015137
https://juejin.im/post/5a4ac6806fb9a045104ad7c8
其他概念
仓库(Repository) 是存放一组关联镜像的集合,比如同一个应用的不同版本的镜像,
注册服务器(Registry) 是存放实际的镜像的地方,
注册索引(Index) 则负责维护用户的账号,权限,搜索,标签等管理。
注册服务器利用注册索引来实现认证等管理。
如何启动容器后,服务进程自启动
构建镜像
FROM centos-base:latestMAINTAINER artemus717@gmail.com
ENTRYPOINT ["/config/bootstrap.sh"]
CMD ["/bin/bash"]
此处指定了ENTRYPOINT、CMD命令.CMD命令为bash,我们构建的镜像一定是有bash进程的,有问题可以直接进行排查.
准备bootstrap.sh
在容器内部,编写脚本,脚本内容直接复制
mkdir /config
mkdir /config/init
vi /config/bootstrap.sh
chmod 755 /config/bootstrap.sh
问题
在你的Dockerfile中
RUN service mysql restart && /tmp/setup.sh
首先,docker镜像不快照您的运行进程,您的RUN命令只是在docker构建阶段运行,您需要指定命令运行时容器开始使用CMD或ENTRYPOINT命令,如 CMD mysql start
其次Docker容器需要进程(最后一个命令)保持运行,否则容器将退出.因此正常的服务mysql启动命令不能直接在Dockerfile中使用
解决方案:为了保持进程正常运行:
通常方式Dockerfile
使用service命令,并附加非end命令后,像tail -F,如下:
CMD service mysql start && tail -F /var/log/mysql/error.log
或使用前台命令来执行此操作,如下:
CMD /usr/bin/mysqld_safe
能否将脚本放到/etc/rc.local 来实现呢?经过测试,貌似不行,原因应该是执行完后,自动退出了当前shell进程.
docker启动流程/关闭流程/状态说明
启动流程
docker run -it ubuntu /bin/bash 首先系统要有一个docker daemon的后台进程在运行,当刚才这行命令敲下时:
- 检查本地是否存在指定的镜像,不存在就从公有仓库下载
- 利用镜像创建并启动一个容器.
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个 ip 地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被终止
最后,我们就得到了一个ubuntu的虚拟机,然后就可以进行各种操作了.
关闭流程
docker stop (默认会10秒超时后再kill)或直接docker kill(直接kill -9) .stop的话,会保持现场到磁盘,优雅的关闭.
容器状态说明
Up 正在运行 Exited 已经停止 Created 已创建未运行
关于持久化
持久化涉及到的外挂数据卷(有状态,紧耦合类的应用)
默认不挂载外部存储,则数据会和容器同生共死(违背了计算和数据分离原则),为容器的迁移或者故障恢复制造了麻烦。
所以一般采用-v参数挂外部卷.rm掉旧容器再重新run一个,把-v的卷挂回去就恢复了。
另外-v可以挂多个物理磁盘或者外部存储,也解决了io瓶颈的问题。
一般设置Volume的场景:
配置文件目录,数据文件目录,重要的日至文件目录.
容器中安装软件
- 安装软件
需要先apt-get update,这个命令的作用是:同步 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的索引,这样才能获取到最新的软件包.否则安装的时候会提示:E: Unable to locate package xxx(如vim)
vim 安装 apt-get install vim
net-tools工具包 (含netstat route ifconfig mii-tool arp 等) apt-get install net-tools
procps工具包 (含 ps free ) apt-get install procps
rcconf (类似ntsyv,图形界面配置服务) apt-get install rcconf
update-rc.d (debian默认已安装,功能类似chkconfig)
iputils-ping (ping功能)
Dockerfile 编写
201807 Chenxin
参考
docker从入门到实践(一直更新): https://yeasy.gitbooks.io/docker_practice/image/build.html
Dockerfile实践: https://www.cnblogs.com/jsonhc/p/7767669.html
xbzj的Dockerfile文件,请参见"k8s笔记内容".
Dockerfile 定制镜像原理说明
dockerfile概念说明
在空目录中,创建Dockerfile文件.Dockerfile指令.大小写不敏感(推荐大写)
- 示例-Dockerfile
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
- 构建-Dockerfile
docker build -t nginx:v3 . # 这里的"."就是context(镜像构建上下文) -t指明创建的镜像名称
镜像构建上下文(Context)
docker build 命令最后有一个"."表示当前目录,这里是指明了上下文路径(context的路径),而非指明Dockerfile文件所在路径.
- docker build 的工作原理
Docker 在运行时分为 Docker 引擎(服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得简单.
当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种C/S的架构中,如何才能让服务端获得本地文件呢?
这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
如果在 Dockerfile 中这么写: COPY ./package.json /app/
这是复制 上下文(context) 目录下的 package.json。因此,COPY 这类指令中的源文件的路径都是相对路径。如果真的需要那些并不在context目录下的文件,应该将它们复制到context目录中去。
如果观察 docker build 输出,我们其实已经看到了这个发送上下文的过程:
$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB ...
一般,应该将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些文件不希望构建时传给 Docker 引擎,那么可以用".dockerignore"(剔除文件列表,不传递给 Docker 引擎).
那么为什么会有人误以为"."是指定 Dockerfile 所在目录呢?在默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。实际上 Dockerfile 的文件名并不要求必须为 Dockerfile,且并不要求必须位于上下文目录中,比如可以用
-f ../docker-file.txt
参数指定某个文件作为 Dockerfile。
当然,一般大家习惯性的会使用默认的文件名 Dockerfile,以及会将其置于镜像构建上下文目录中。
Dockerfile指令说明
FROM
指定基础镜像
LABEL
可选的
RUN
执行命令.制作image过程中执行的事项.每执行1次RUN,就会构建1个临时image.
其格式有两种:
* shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样.RUN <cmd> 这个会当作/bin/sh -c “cmd” 运行。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
* exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。
RUN ["executable", "arg1", .. ],Docker把他当作json的顺序来解析,因此必须使用双引号,而且executable需要完整路径.
Dockerfile 中每一个指令都会建立一层.很多初学者制作出很臃肿的镜像原因之一,就是忘记了每一层构建的最后没有清理掉无关文件.
CMD
执行指令或传递参数
执行container时候的默认启动命令.可能会被覆盖(比如,当运行container的时候声明了command,则不再用image中的CMD定义的命令)
一个Dockerfile中定义多个CMD的时候,只有最后一个才会起作用.
CMD定义的三种方式:
CMD <cmd> 这个会当作/bin/sh -c "cmd"来执行 # shell方式
CMD ["executable","arg1",....] # 推荐exec方式
CMD ["arg1","arg2"],这个时候CMD作为ENTRYPOINT的参数
对容器而言,启动程序就是启动容器应用进程. 容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,不会关心辅助进程.
而使用 service nginx start 命令,则是希望用 upstart 方式以后台守护进程形式启动 nginx 服务。而刚才说了 CMD service nginx start 会被理解为 CMD [ "sh", "-c", "service nginx start"],因此主进程实际上是 sh。那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。
正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:
CMD ["nginx", "-g", "daemon off;"]
EXPOSE
声明端口 EXPOSE <端口1> [<端口2>...]
声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。此声明有两个好处
1.帮助使用者理解服务的守护端口,以便配置映射.
2.启动容器时,指定 docker run -P 时,会将EXPOSE 端口映射到物理机的随机端口.
ENTRYPOINT
当定义了ENTRYPOINT以后,CMD只能够作为参数进行传递.
有2种定义方式:
1.ENTRYPOINT ["executable","arg1","arg2"] (推荐) CMD可以通过json的方式来定义entrypoint的参数,在运行容器的时候可以通过CMD的方式传递参数.执行的程序的pid为1.
2.ENTRYPOINT cmd param1 param2 (shell form) 相当于/bin/bash -c "cmd"命令.bash的pid为1.会屏蔽掉docker run时后面加的命令和CMD里的参数.不常用.
示例,第一种,比如:
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
把可能需要变动的参数写到CMD里面。然后你可以在docker run里指定参数,这样CMD里的参数(这里是-c)就会被覆盖掉,而ENTRYPOINT里的不会被覆盖。
ENTRYPOINT更像是一个可执行程序,而CMD是随时都可能被覆盖的命令或传参.
COPY & ADD
不建议用ADD.
类似于直接在bash里执行的指令是 docker container cp [containID]:[/path/to/file] /local/path/dir/ (容器文件拷贝到物理机).
把host上的文件或者目录(源文件必须在context路径下)复制到image中.如果目录不存在会在复制文件前先行创建缺失目录.
不建议使用ADD(因为语义模糊,且没必要)
ADD相对COPY多的功能(支持URL),当src为网络URL的情况下,ADD指令可以把它下载到dest的指定位置.ADD相对COPY多的功能,能够进行自动解压压缩包.
ENV
ENV
后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。
WORKDIR
用来改变工作目录.
比如:
RUN cd /app
RUN echo "hello" > world.txt
这样做根本不会生成/app/world.txt文件.原因是,每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。
因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。
USER
改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份.
扩展知识: 如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu。
#建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
#下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
#设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ] #exec的说明,执行shell脚本三种方式的区别( sh,bash,xxx.sh / source,. / exec ) 见<<shell bash 技巧说明>>笔记.
VOLUME
Dockerfile中的VOLUME使每次运行一个新的container时,都会为其自动创建一个匿名的volume,并挂载到container指定的目录里.
应用场景: 用来创建一个在image之外的mount point,可以用来在多个container之间实现数据共享(通过--volumes-from吗?).
运行使用json array的方式定义多个volume.
VOLUME ["/var/data1","/var/data2"] .
或者plain text的情况下定义多个VOLUME指令.
示例,cat Dockerfile
FROM nginx
#VOLUME ["/home/admin/test"] 或 #VOLUME ["/home/admin/test","home/admin/mynginx"] 或
VOLUME /home/admin/test
在docker container run xxx 的时候,会创建一个匿名volume.比如,物理机上的/var/lib/docker/volumes/99b2...d1/_data -> 容器内的/home/admin/test .
在 Dockerfile 中添加数据卷
在 Dockerfile 中我们可以使用 VOLUME 指令向容器添加数据卷:
VOLUME /data # 这里只跟容器有关,因为为了匹配不同物理机卷环境,所以物理机卷必是匿名卷.
在使用 docker build 命令生成镜像并且以该镜像启动容器时会挂载一个主机数据卷到容器 /data 目录。根据我们已知的数据覆盖规则,如果镜像中存在 /data 目录,镜像中的/data目录的内容将全部被复制到宿主机中对应的目录中(匿名目录),并且根据容器中的文件设置合适的权限和所有者。假设在docker run 的时候,添加 -v /data:/data(前一个是物理主机目录,且有数据,后一个是新启动容器目录) ,会发生什么呢?应该会以物理机数据优先吧.
注意,VOLUME 指令不能挂载主机中指定的目录。这是为了保证 Dockerfile 的一致性,因为不能保证所有的宿主机都有对应的目录。
在实际的使用中,这里还有一个陷阱需要大家注意:在 Dockerfile 中使用 VOLUME 指令之后的代码,如果尝试对这个数据卷进行修改,这些修改都不会生效!下面是一个这样的例子:
FROM ubuntu
RUN useradd nick
VOLUME /data # 其实把VOLUME当做声明就可以了,不实际创建卷.这样理解.
RUN touch /data/test.txt
RUN chown -R nick:nick /data
通过这个 Dockerfile 创建镜像并启动容器后,该容器中存在用户 nick,并且能够看到 /data 目录挂载的数据卷。但是 /data 目录内并没有文件 test.txt,更别说 test.txt 文件的所有者属性了。要解释这个现象需要我们了解通过 Dockerfile 创建镜像的过程:
Dockerfile 中除了 FROM 指令的每一行都是基于上一行生成的临时镜像运行一个容器,执行一条指令并执行类似 docker commit 的命令得到一个新的镜像。这条类似 docker commit 的命令不会对挂载的数据卷进行保存。
所以上面的 Dockerfile 最后两行执行时,都会在一个临时的容器上挂载 /data,并对这个临时的数据卷进行操作,但是这一行指令执行并提交后,这个临时的数据卷并没有被保存。因而我们最终通过镜像创建的容器所挂载的数据卷是没有被最后两条指令操作过的。我们姑且叫它 "Dockerfile 中数据卷的初始化问题"。
下面的写法可以解决 Dockerfile 中数据卷的初始化问题:
FROM ubuntu
RUN useradd nick
RUN mkdir /data && touch /data/test.txt # 创建了/data目录
RUN chown -R nick:nick /data
VOLUME /data # 在声明前,镜像里已经有/data目录了,那么会将目录以及内容复制到启动的容器匿名目录里(对应容器的/data目录).
通过这个 Dockerfile 创建镜像并启动容器后,数据卷的初始化是符合预期的。这是由于在挂载数据卷时,/data 已经存在,/data 中的文件以及它们的权限和所有者设置会被复制到数据卷中(被复制到物理主机的匿名卷中,对应的就是新启动的容器的/data目录)。
还有另外一种方法可以解决 Dockerfile 中数据卷的初始化问题。就是利用 CMD 指令和 ENTRYPOINT 指令的执行特点:与 RUN 指令在镜像构建过程中执行不同,CMD 指令和 ENTRYPOINT 指令是在容器启动时执行。因此使用下面的 Dockerfile 也可以达到对数据卷的初始化目的:
FROM ubuntu
RUN useradd nick
VOLUME /data
CMD touch /data/test.txt && chown -R nick:nick /data && /bin/bash
HEALTHCHECK
健康检查
ONBUILD:略
如何编写最佳的Dockerfile(基本为网上内容)
目标:
- 更快的构建速度
- 更小的Docker镜像大小
- 更少的Docker镜像层
- 充分利用镜像缓存
- 增加Dockerfile可读性
- 让Docker容器使用起来更简单
总结(编写Dockerfile需要注意的问题)
- 编写.dockerignore文件
- 容器只运行单个应用
- 将多个RUN指令合并为一个
- 基础镜像的标签不要用latest
- 每个RUN指令后删除多余文件
- 选择合适的基础镜像(alpine版本最好)
- 设置WORKDIR和CMD
- 使用ENTRYPOINT (可选)
- 在entrypoint脚本中使用exec
- COPY与ADD优先使用前者
- 合理调整COPY与RUN的顺序
- 设置默认的环境变量,映射端口和数据卷
- 使用LABEL设置镜像元数据
- 添加HEALTHCHECK
示例
最初示例
示例Dockerfile犯了几乎所有的错.假设我们需要使用Docker运行一个Node.js应用,下面就是它的Dockerfile(CMD指令太复杂了,所以我简化了,它是错误的,仅供参考)。
FROM ubuntu
ADD . /app
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install -y nodejs ssh mysql
RUN cd /app && npm install
# this should start three processes, mysql and ssh
# in the background and node app in foreground
# isn't it beautifully terrible? <3
CMD mysql & sshd & npm start #这里是错误的
构建镜像:
docker build -t wtf .
这里先给出最终示例(对比):
FROM node:7-alpine
ENV PROJECT_DIR=/app
WORKDIR $PROJECT_DIR
COPY package.json $PROJECT_DIR
RUN npm install
COPY . $PROJECT_DIR
ENV MEDIA_DIR=/media \
NODE_ENV=production \
APP_PORT=3000
VOLUME $MEDIA_DIR
EXPOSE $APP_PORT
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]
将最初示例逐步进行优化的过程
-
编写.dockerignore文件
构建镜像时,Docker需要先准备context ,将所有需要的文件收集到进程中。默认的context包含Dockerfile目录中的所有文件,但是实际上,我们并不需要.git目录,node_modules目录等内容。示例如下:
.git/
node_modules/ -
容器只运行单个应用
因此,我建议大家为每个应用构建单独的Docker镜像,然后使用 Docker Compose 运行多个Docker容器。
现在,我从Dockerfile中删除一些不需要的安装包,另外,SSH可以用docker exec替代。示例如下:
FROM ubuntu
ADD . /app
RUN apt-get update
RUN apt-get upgrade -y
# we should remove ssh and mysql, and use
# separate container for database
RUN apt-get install -y nodejs # ssh mysql
RUN cd /app && npm install
CMD npm start
-
将多个RUN指令合并为一个
Docker镜像是分层的.Docker镜像类似于洋葱。它们都有很多层。为了修改内层,则需要将外面的层都删掉。记住这一点的话,其他内容就很好理解了。
现在,我们将所有的RUN指令合并为一个。
记住一点,我们只能将变化频率一样的指令合并在一起。将node.js安装与npm模块安装放在一起的话,则每次修改源代码,都需要重新安装node.js,这显然不合适. -
基础镜像的标签不要用latest
当镜像没有指定标签时,将默认使用latest 标签。当镜像更新时,latest标签会指向不同的镜像,这时构建镜像有可能失败。
示例Dockerfile应该使用16.04作为标签。 -
每个RUN指令后删除多余文件
假设我们更新了apt-get源,下载,解压并安装了一些软件包,它们都保存在/var/lib/apt/lists/目录中。但是,运行应用时Docker镜像中并不需要这些文件。我们最好将它们删除,因为它会使Docker镜像变大。
示例Dockerfile中,我们可以删除/var/lib/apt/lists/目录中的文件(它们是由apt-get update生成的)。
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y nodejs \
# added lines
&& rm -rf /var/lib/apt/lists/*
ADD . /app
RUN cd /app && npm install
CMD npm start
- 选择合适的基础镜像(alpine版本最好)
在示例中,我们选择了ubuntu作为基础镜像。但是我们只需要运行node程序,有必要使用一个通用的基础镜像吗?node镜像应该是更好的选择。
FROM node
...
更好的选择是alpine版本的node镜像。alpine是一个极小化的Linux发行版,只有4MB,这让它非常适合作为基础镜像。
FROM node:7-alpine
ADD . /app
RUN cd /app && npm install
CMD npm start
- 设置WORKDIR和 CMD
WORKDIR指令可以设置默认目录,也就是运行RUN / CMD / ENTRYPOINT指令的地方。
CMD指令可以设置容器创建时执行的默认命令。另外,你应该将命令写在一个数组中,数组中每个元素为命令的每个单词.
FROM node:7-alpine
WORKDIR /app
ADD . /app
RUN npm install
CMD ["npm", "start"]
- 使用ENTRYPOINT (可选)
ENTRYPOINT指令并不是必须的,因为它会增加复杂度。
ENTRYPOINT可以是一个脚本,会默认执行。它通常用于构建可执行的Docker镜像。
示例Dockerfile:
FROM node:7-alpine
WORKDIR /app
ADD . /app
RUN npm install
ENTRYPOINT ["./entrypoint.sh"] #该脚本可以接受dev,start,*等参数.
CMD ["start"]
可以使用如下命令运行该镜像:
# 运行开发版本 docker run our-app dev
# 运行生产版本 docker run our-app start
# 运行bash版本 docker run -it our-app /bin/bash
-
在entrypoint脚本中使用exec
在前文的entrypoint脚本中,我使用了exec命令运行node应用。不使用exec的话,我们则不能顺利地关闭容器,因为SIGTERM信号会被bash脚本进程吞没。exec命令启动的进程可以取代脚本进程,因此所有的信号都会正常工作。 -
COPY与ADD优先使用前者
COPY指令非常简单,仅用于将文件拷贝到镜像中。ADD相对来讲复杂一些,可以用于下载远程文件以及解压压缩包.
FROM node:7-alpine
WORKDIR /app
COPY . /app
RUN npm install
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]
- 合理调整COPY与RUN的顺序
我们应该把变化最少的部分放在Dockerfile的前面,这样可以充分利用镜像缓存。
示例中,源代码会经常变化,则每次构建镜像时都需要重新安装NPM模块,这显然不是我们希望看到的。因此我们可以先拷贝package.json,然后安装NPM模块,最后才拷贝其余的源代码。这样的话,即使源代码变化,也不需要重新安装NPM模块。
FROM node:7-alpine
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]
- 设置默认的环境变量,映射端口和数据卷(相对于上面的11项,这里比较完整归纳了上面遇到的问题)
运行Docker容器时很可能需要一些环境变量。在Dockerfile设置默认的环境变量是一种很好的方式。
另外,我们应该在Dockerfile中设置映射端口和数据卷。
示例如下:
FROM node:7-alpine
ENV PROJECT_DIR=/app
WORKDIR $PROJECT_DIR
COPY package.json $PROJECT_DIR
RUN npm install
COPY . $PROJECT_DIR
ENV MEDIA_DIR=/media \
NODE_ENV=production \
APP_PORT=3000
VOLUME $MEDIA_DIR
EXPOSE $APP_PORT
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]
ENV指令指定的环境变量在容器中可以使用。如果你只是需要指定构建镜像时的变量,你可以使用ARG指令。
-
使用LABEL设置镜像元数据
使用LABEL指令,可以为镜像设置元数据,例如镜像创建者或者镜像说明。
旧版的Dockerfile语法使用MAINTAINER指令指定镜像创建者,但是它已经被弃用了。
有时,一些外部程序需要用到镜像的元数据,例如nvidia-docker需要用到com.nvidia.volumes.needed。
示例如下:
FROM node:7-alpine
LABEL maintainer "jakub.skalecki@example.com"
... -
添加HEALTHCHECK
运行容器时,可以指定--restart always选项。这样的话,容器崩溃时,Docker守护进程(docker daemon)会重启容器。对于需要长时间运行的容器,这个选项非常有用。但是,如果容器的确在运行,但是不可(陷入死循环,配置错误)用怎么办?使用HEALTHCHECK指令可以让Docker周期性的检查容器的健康状况。我们只需要指定一个命令,如果一切正常的话返回0,否则返回1。
示例如下:
FROM node:7-alpine
LABEL maintainer "jakub.skalecki@example.com"
...
EXPOSE $APP_PORT
HEALTHCHECK CMD curl --fail http://localhost:$APP_PORT || exit 1
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]
当请求失败时,curl --fail 命令返回非0状态。
docker compose 单主机容器编排
20180728 Chenxin
Docker是容器技术的核心、基础.
Docker Compose是一个基于Docker的单主机容器编排工具,功能并不像Docker Swarm和Kubernetes是基于Dcoker的跨主机的容器管理平台那么丰富。
这个也挺常用的,将一组服务的多个docker一起管理.具体需要深入研究
你说多服务?好吧那就写个docker-compose.file吧。 嗯哼? 你说集群部署 来来来, 有Kubernetes、Mesos,Fleet和Swarm 任君挑选
https://docs.docker.com/compose/
https://blog.csdn.net/pushiqiang/article/details/78682323
https://www.cnblogs.com/neptunemoon/p/6512121.html
Compose 中有两个重要的概念:
服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。
Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
安装:
pip install docker-compose 如果报错,添加参数 --ignore-installed docker-compose