docker 常用命令
映像相关操作
- 获得在线映像
$docker pull imagename:verision
容器相关操作
- 创建容器:
$docker run -itd --name haha -p 9999:8080 centos:6.7 /bin/bash
,-i
:交互式操作,-t
:终端,-d
:后台运行,指定--name
容器名称为:haha
,-p 8080:8080
将容器中的8080
端口映射为宿主机的9999
端口. - 查看当前所有容器的状态:
$docker ps -a
- 启动容器:
$docker start [id/names]
- 停止容器:
$docker stop [id/names]
- 重启容器:
$docker restart [id/names]
- 进入容器:
$docker attach [id/names]
或$docker exec -it [id/names] /bin/bash
,第一种方式退出容器的时候容器也会退出. - 查看容器日志:
$docker logs -f [id/names]
- 删除容器:
$docker rm -f [id/names]
最简单的镜像构建
有如下go程序,编译成名为docker-package的二进制文件,
需要部署到docker中运行
1 | package main |
- 编写Dockerfile
1 | FROM nginx |
- 构建镜像,镜像的名称为mysoft
1
sudo docker build -t mysoft .
- 输出结果
1
2
3
4$ sudo docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
mysoft latest 7d8e2a754d61 9 seconds ago 149MB
nginx latest 4f380adfc10f 6 days ago 133MB - 执行这样可以从浏览器访问服务了。
1
2sudo docker run --name web3 -d -p 80:80 -p 8088:8088 mysoft:latest
0bc7ef9a4875de4cb6968271fde10f075d7397b6a42ceb256da3a37b78dfd99f
还有另外一种方法,将程序发送到容器中,然后将整个容器打包。
docker build命令
构建的命令格式docker build [选项] <上下文路径/url/->
Docker运行时分成Docker引擎和客户端
graph LR client --> server
Docker引擎提供一组REST API接口,称为Docker Remote API
.docker命令作为客户端工具。调用的docker命令其实调用远端Docker引擎的服务。由于Docker引擎是服务端,服务端需要获取本机的文件。需要指定上下文路径。那么可以将Dockerfile文件挪到外部的文件夹中,将需要构建的数据放在另一个文件夹。指定上下文路径为资源路径。sudo docker build -t mysoft ./docker_package
同样可以进行构建。
除了文件下面放Dockerfile文件,还可以使用其他名字,使用-f
引入Dockerfile文件。sudo docker build -t mysoft -f MyDockerFile ./
其他构建方式
Git repo构建
docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world
指定master分支,构建目录为amd64/hello-world
tar压缩包构建
docker build http://server/context.tar.gz
docker引擎会下载tar包,解压构建标准输入构建
docker build - < Dockerfile
或cat Dockerfile | docker build -
读取压缩包构建
docker build - < context.tar.gz
golang二进制构建镜像
由如下go程序
1 | package main |
1 | FROM scratch |
构建docker build -t mysoft .
执行:docker run mysoft param1 param2
输出结果:
1 | sudo docker run mysoft:latest 3141 41515 --- 5151 |
docker上下文路径
build后面的.表示上下文路径为当前路径
Dockerfile命令
Dockerfile里面包含着一条有一条的指令,每个指令构建一层。
FROM
指定基础镜像,该条指令必须有并且是第一条指令.常见的基础镜像nginx,redis,mongo,mysql,httpd,php,tomcat等。语言镜像,node、openjdk、python、ruby、golang等。基础的操作系统镜像ubuntu、debian、centos、fedora、alpine。空白镜像scratch,接下来的指令都是作为第一层,不需要操作系统提供支持。
RUN
使用RUN命令来执行命令行命令的。常见的格式
shell格式:
RUN <命令>
RUN echo 'HelloWorld' > /usr/share/release.txt
exec格式:
RUN ["可执行文件", "参数1", "参数2"]
每个指令都会建立一层,每一个RUN指令都会建立一层,执行完毕之后commit该层,形成新的镜像。Union FS最大层数限制,不超过127层。
一个错误的构建例子:
1 | FROM debian:stretch |
正确的例子:
1 | FROM debian:stretch |
构建每一层之后一定要清理无关的文件。
COPY
格式
1 | COPY [---chown=<user>:<group>] <源路径>... <目标路径> |
源路径可以是多个,可以是通配符规则的路径。目标路径可以是工作路径.绝对路径。
工作路径需要用WORKDIR
指定。并且需要提前创建
源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等
使用--chown=<user>:<group>
改变文件所属用户及所属组。
1 | COPY --chown=55:mygroup files* /mydir/ |
源路径为文件夹的话,不会复制文件夹,而是复制文件夹里面的内容到目标路径。
ADD
ADD和COPY格式基本一致,源路径是URL的话,会下载这个文档到目标路径去,并且设置文档权限为600。如果源路径是tar文件的话,压缩格式为gzip
, bzip2
,xz
的话会自动解压文件到目标路径去。
1 | FROM scratch |
尽量使用COPY命令,ADD会包含更复杂的功能。推荐所有文件复制都是使用COPY,只有在自动解压的场合使用ADD.
CMD
容器启动命令,
- shell格式:
CMD <命令>
- exec格式:
CMD ["可执行文件", "参数1","参数2", "...."]
- 参数列表格式:
CMD ["参数1", "参数2", ....]
参数列表格式和exec格式有什么区别,参数列表格式是指定ENTRPOINT
指令之后,使用CMD
指定具体的参数。
Docker的容器作为一个进程,启动容器的时候需要指定所运行的程序及参数。CMD命令用来指定容器主进程的启动命令。
使用exec格式会被解析成json数组,需要使用双引号。使用shell格式会被包装成sh -c
的参数执行。CMD echo $HOME
会被变更为CMD ["sh", "-c", "echo $HOME"]
Docker容器和虚拟机不一样,容器中的应用都应该以前台执行。容器内没有后台服务概念。启动程序就是容器的应用进程,容器就是为了主进程存在的,主进程退出,容器失去了存在的意义。
使用CMD service nginx start
会转成CMD ["sh", "-c", "service nginx start"]
,主进程是sh,执行命令结束之后,主进程退出。直接运行nginx二进制文件CMD ["nginx", "-g", "daemon off"]
并且前台运行。
ENTRYPOINT
指令格式和RUN一样,ENTRYPOINT在运行时可以替代,需要通过docker run
的--entrypoint
来指定。当前指定了ENTRYPOINT
之后,CMD的命令内容作为ENTRYPOINT
的参数了
场景:
1.让镜像变成命令一样使用
1 | FROM ubuntu:18.04 |
构建docker build -t myip .
,使用curl镜像docker run myip
镜像会执行CMD的内容curl -s http://myip.ipip.net
,这时候已经固定死入参了。
想要直接使用镜像像curl一样的调用。需要改成下面的
1 | FROM ubuntu:18.04 |
使用docker run myip -i
,这时候-i作为CMD会被当做参数传给ENTRYPOINT
所以最终执行的命令是curl -s http://myip.ipip.net -i
2.应用运行前的准备工作
容器启动就是主进程,进入主进程之前需要进行准备工作。避免使用root用户启动服务,提高安全性。
将预处理的工作放入脚本中,然后使用ENTRYPOINT执行,这个脚本接收到参数由CMD传入
1 | FROM alpine:3.4 |
执行的顺序:docker-entrypoint.sh redis-server
CMD的值作为ENTRYPOINT的入参.脚本中判断入参
1 | !/bin/sh |
ENV设置环境变量
想要启动的时候设置操作系统的环境变量,需要用到ENV命令
格式:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>....
使用例子:1
2
3
4FROM nginx
ENV VERSION=1.1.1 \
MESSAGE="TEST MESSAGE FROM ENV"
RUN echo "<h1> Hello, This is a image for ENV, VERSION=${VERSION}, MESSAGE=${MESSAGE} </h1>" > /usr/share/nginx/html/index.html
设置的环境变量能够在Dockerfile里面展开ADD,COPY,ENV,EXPOSE,FROM,LABEL,USER,WORKDIR,VOLUME,STOPSIGNAL,ONBUILD,RUN
。可以使用设置环境变量中的某个版本,然后就可以构建不同的镜像。
1 | curl localhost |
ARG构建参数
格式:ARG <参数名>[=<默认值>]
设置环境变量,和ENV不同的是在容器运行的时候是不存在的。不要保存密码之类的东西,因为docker history还是可以看得见的。
该默认值可以使用docker build
中使用--build-arg <参数名>=<值>
来覆盖。
在FROM之前使用指定,只能使用在FROM命令中.FROM之后想要使用该ARG必须重新指定才行。
1 | # 只在 FROM 中生效 |
VOLUME定义匿名卷
格式:
VOLUME ["<路径1>", "<路径2>"....]
VOLUME <路径>
1 | VOLUME /data |
在构建之前可以将某些目录挂载为卷,这样容器向目录写东西的时候不会影响存储层。运行容器时可以覆盖挂载设置。docker run -d -v mydata:/data xxx
写一个程序,这个程序是运行的时候往/data路径下创建文件并写入HelloWorld
1 | package main |
Dockerfile配置
1 | FROM scratch |
这样,定义了一个匿名卷/tmp, 二进制中写入的数据会往匿名卷/tmp写入数据。执行时的将/tmp
映射成宿主机的/work/learngolang/
路径。sudo docker run --name soft3 -v /work/learngolang/:/tmp mysoft2:latest
ARG构建参数
格式:ARG <参数名>[=<默认值>]
设置环境变量,和ENV不同的是在容器运行的时候是不存在的。不要保存密码之类的东西,因为docker history还是可以看得见的。
该默认值可以使用docker build
中使用--build-arg <参数名>=<值>
来覆盖。
在FROM之前使用指定,只能使用在FROM命令中.FROM之后想要使用该ARG必须重新指定才行。
1 | # 只在 FROM 中生效 |
VOLUME定义匿名卷
格式:
VOLUME ["<路径1>", "<路径2>"....]
VOLUME <路径>
1 | VOLUME /data |
在构建之前可以将某些目录挂载为卷,这样容器向目录写东西的时候不会影响存储层。运行容器时可以覆盖挂载设置。docker run -d -v mydata:/data xxx
写一个程序,这个程序是运行的时候往/data路径下创建文件并写入HelloWorld
1 | package main |
Dockerfile配置
1 | FROM scratch |
这样,定义了一个匿名卷/tmp, 二进制中写入的数据会往匿名卷/tmp写入数据。执行时的将/tmp
映射成宿主机的/work/learngolang/
路径。sudo docker run --name soft3 -v /work/learngolang/:/tmp mysoft2:latest
EXPOSE 暴露端口
格式EXPOSE <端口1> [<端口2>...]
声明容器运行时提供服务的端口,只是声明。不会因为声明就启动这个端口。好处:
- 可以了解这个镜像服务的守护端口,方便做映射
- 在运行时使用随机端口映射.
docker run -P
自动随机映射EXPOSE的端口
仅仅声明要使用的端口
WORKDIR
格式:WORKDIR <工作目录路径>
使用WORKDIR
指令可以来指定工作目录(或者当前目录),以后各层的当前目录就被变更为指定目录.不指定会帮助建立目录。怎么理解?
例子:
想要在/app
目录下写一个,world.txt文件
1 | RUN cd /app |
这么写是不起作用的.这个world.txt最终会放在/world.txt
,RUN命令是独立的进程,形成独立的层。不会影响下面的RUN命令。这个和shell命令不一样。
指定工作目录
1 | WORKDIR /app |
这样创建如下文件
1 | /app/world.txt |
USER
指定当前用户
格式:USER <用户名>:[:<用户组>]
影响以后构建的层。为什么要指定用户呢?首先,搭建配置环境的时候使用root用户是可以避免一种意外。然后应用在自己的用户下运行,redis、postgres用户等.必须先建立用户才能转到用户。
1 | RUN groupadd -r redis && useradd -r -g redis redis |
以root执行的脚本,想要在执行期间改变身份。不要使用su或sudo,使用gosu
是个比较好的选择.
1 | # 建立 redis 用户,并使用 gosu 换另一个用户执行命令 |
HEALTHCHECK
格式:
HEALTHCHECK [选项] CMD <命令>
:设置检查容器健康状况的命令HEALTHCHECK NONE
:屏蔽掉基础镜像的健康检查指令.
初始状态为starting,HEALTHCHECK
检查成功之后变成HEALTHY,连续失败一定次数失败变更为unhealthy
。
选项:--interval=<间隔>
: 两次健康检查的间隔,默认30秒.--timeout=<时长>
: 检查命令的运行超时时间,超过这个时间,本次健康检查视为失败,默认30秒超时。--retries=<次数>
: 连续失败次数,失败到该次数之后视为unheathy,默认3次。
多个健康检查命令,只有最后一命令有效。
CMD的格式和ENTRYPOINT
一样,分成shell
格式和exec
格式。调用返回值确定检查成功与否0
:成功,1
:失败,2
:保留,不要使用这个值
ONBUILD
格式:ONBUILD <其他指令>
其他指令就是RUN、COPY这种。但是在构建该镜像的时候是不会执行的,只有在以该镜像为基础镜像构建的镜像构建时才会执行。延迟执行,定义了一组构建规则,但是这个构建规则是使用该镜像作为基础镜像的时候才会触发。
有几个应用的docker构建脚本使用相同的命令和资源,新建一个镜像需要将这些配置命令复制过去其实是没啥问题。但是维护起来就是有问题的,有个地方有bug,所有复制过去的Dockerfile都要变更。这时候得把公共的构建命令配置抽出来,形成基础镜像。但是普通的命令涉及到配置文件就直接写入镜像了。
比如:
1 | COPY configure.txt /app/config.txt |
但是,构建base镜像的时候必须要确定configure.txt的文件内容。但是需求是每个镜像都想要自己定义的configure.txt的,这时候得需要使用ONBUILD命令
1 | ONBUILD COPY configure.txt /app/config.txt |
这时候构建base镜像,这个复制的命令是不运行的。只有使用该基础镜像构建新镜像的时候,才会调用.
1 | FROM base |
构建A镜像,才会将configure.txt文件复制入镜像,这样configure可以为每个应用镜像单独配置,但是配置的命令不需要多次写。
LABEL
为镜像添加元数据LABEL <key>=<value> <key>=<value> <key>=<value>.....
可以用元数据来申明镜像的作者\相关文档。
SHELL
格式:SHELL ["executable", "parameters"]
SHELL
指令可以指定RUN,ENTRYPOINT,CMD指令的SHELL.
linux中默认为["/bin/sh", "-c"]
例子,注释为实际执行的。
1 | SHELL ["/bin/sh", "-cex"] |
1 | SHELL ["/bin/sh", "-cex"] |
Dockerfile多阶段编译
Docker 17.05版本之前,可以将所有构建放入一个Dockerfile中,也可以分开写Dockerfile,然后使用脚本整合起来.Docker v17.05开始支持多阶段构建。
使用as
命令为某一阶段命名,在构建的时候添加target=xxx
参数就可以了
例子:
1 | FROM golang:alpine as builder |
只构建某一阶段的镜像
只构建某一阶段的镜像使用as
来命名,定义一个builder的构建阶段FROM golang:alpine as builder
构建这个阶段的镜像docker build --target builder -t username/imagename:tag .
构建过程中从其它镜像复制文件
使用COPY --from
来指定镜像COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
也可以从上一步构建的镜像中指定。COPY --from=0
多系统架构支持的Docker镜像
可以使用镜像名称作为不同架构的镜像,arm64v8
….
使用manifest
列表(manifest list)来区分不同架构的镜像,
获得镜像的流程,Docker引擎查找这个镜像是否有manifest
列表,有的话会按照当前Docker运行的系统架构获得对应的镜像。如果没有的话就直接获得镜像。
查看golang:alpine
镜像的manifest列表docker manifest inspect golang:alpine
1 | { |
可以看出,不同的系统,架构都有对应的镜像。
构建多架构镜像
查看manifest列表
docker manifest inspect username/test
创建manifest列表
1 | $docker manifest create MANIFEST_LIST MANIFEST [MANIFEST...] |
修改manifest
列表时使用-a
或--amend
参数
设置manifest列表
1 | $ docker manifest annotate [OPTIONS] MANIFEST_LIST MANIFEST |
推送manifest列表
docker manifest push username/test
其他构建镜像的方式
从rootfs压缩包导入
- 格式
docker import [选项]<文件>|<URL>|-[<仓库名>[:<标签>]]
压缩包可以是本地文件,远程web文件,标准输入,解压之后在镜像的/
目录展开,作为第一层提交1
2
3
4
5
6docker import \
http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz \
openvz/ubuntu:16.04
Downloading from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz
sha256:412b8fc3e3f786dca0197834a698932b9c51b69bd8cf49e100c35d38c9879213
镜像的导入和导出
docker save
和docekr load
save
1 | $ docker save alpine -o filename |
load
1 | docker load -i alpine-latest.tar.gz |
高级传输,通过管道docker save <镜像名> | bzip2 | pv | ssh <用户名>@<主机名> 'cat | docker load'
镜像分层的原理
使用UnionFS
,将每一层独立出来。
通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。
Docker 在 OverlayFS 上构建的容器也是利用了类似的原理。
仓库(Repository)是集中存放镜像的地方。和注册服务器(Registry)概念不一样,注册服务器是负责管理仓库的,每个服务器上面可以有多个仓库,每个仓库都可以有多个镜像docker.io/ubuntu
仓库,docker.io
是注册服务器的地址,ubuntu
是仓库的名称。
Docker Hub
Docker Hub是官方维护的公共仓库,
登录
docker login
登录,docker logout
退出登录
拉取镜像
docker search
查找镜像,docker pull
下来
推送镜像
docker push username/image
自动构建
仅作为付费用户使用
私有仓库
使用docker-registry
构建私有仓库
安装运行
1 | docker run -d \ |
私有仓库的镜像将会被放在本地的/opt/data/registry
目录
推送镜像
打tag$sudo docker tag soft3:latest 127.0.0.1:5000/soft3:latest
推送镜像错误:
1 | $sudo docker push 172.0.0.1:5000/soft3:lastest |
解决办法:
在/etc/docker/daemon.json
写入下面的内容
1 | { |
删除已有镜像之后再次pull就可以了。
私有仓库的高级配置
准备好证书文件
私有仓库的默认配置文件位于/etc/docker/registry/config.yml
文件中.
1 | version: 0.1 |
生成http认证文件
1 | mkdir auth |
编辑docker-compose.yml
1 | version: '3' |
修改hosts,/etc/hosts
文件127.0.0.1 docker.domain.com
启动docker-compose up -d
Docker数据管理
数据卷
可供一个或多个容器使用的特殊目录,绕过UFS
。特性:
数据卷
可以在容器之间共享和重用- 对
数据卷
的修改会立马生效 - 对
数据卷
的更新,不会影响镜像 数据卷
默认会一直存在,即使容器被删除
类似linux下目录文件进行mount,镜像中被指定为挂载点的目录中的文件会复制到数据卷中。(仅数据为空时复制)
创建一个数据卷
docker volume create my-vol
查看数据卷
1 | docker volume ls |
查看my-vol内容
1 | docker volume inspect my-vol |
挂载数据卷
使用docker run
命令的时候使用--mount
标记来将数据卷
挂载到容器里,一次run可以挂载多个数据卷。
加载一个数据卷到web容器的/usr/share/nginx/html
目录中
1 | docker run -d -p 80:80 \ |
查看挂载详情:
1 | "Mounts": [ |
直接变更宿主机的/var/lib/docker/volumes/my-vol/_data
里面的index.html就可以了。程序和数据分离了。
删除数据卷
1 | docker volume rm my-vol |
删除容器的时候顺带删除数据卷,docker rm -v
删除无主的数据卷:docker volume prune
挂载主机目录作为一个数据卷
数据卷由docker引擎负责管理,还有一种方法是直接将本地的目录挂载到容器中去。并且设为容器只读
1 | docker run -d -p 80:80 \ |
挂载一个本地主机文件作为数据卷
挂载单个文件到容器中
1 | docker run --rm -it \ |
例子:挂载nginx的配置文件
对于nginx:alpine镜像来说,配置文件路径/etc/nginx/nginx.conf
,那么可以把本地的配置挂载到容器中,让容器使用一套配置来运行,使用相同的静态界面,也可以挂载上去
1 | docker run -d -p 80:80 --name web --mount type=bind,source=/work/soft/nginx/nginx.conf,target=/etc/nginx/nginx.conf --mount type=bind,source=/work/soft/nginx/www,target=/usr/share/nginx/html nginx:alpine |
一个完整例子:
1 | !/bin/sh |
会将原来的容器关闭删除并且重启容器。
docker容器需要访问外部网络,需要本地系统转发和支持。linux检查网络转发功能是否打开
1 | sysctl net.ipv4.ip_forward |
0为未打开,1为打开。手动打开转发的服务设定sysctl -w net.ipv4.ip_forward=1
。启动Docker服务的时候设定--ip-forward=true
,docker也会将系统ip_forward
参数。
容器之间访问
容器之间相互访问,需要容器网络拓扑已经相连,所有容器连接到docker0
网桥上并且本地系统的防火墙软件–iptables
是否允许通过。ip分配机制是怎么样的?如何让容器每次执行都能获得固定的ip?