Docker 命令、Dockerfile 指令与一次完整构建

Docker 日常要用的东西分三层:命令、Dockerfile 指令、构建流程。命令层决定你怎么查状态和进容器,指令层决定镜像怎么被描述出来,构建层决定同样的应用换人做能不能得到同一个结果。这篇按这三层记一遍,环境是 CentOS 7、Docker Engine 20.10.12。

先看引擎自己说了什么

docker version 的输出分成 Client 和 Server 两段,Client 是命令行工具,Server(Engine)才是真正干活的一端,下面还挂着 containerd、runc、docker-init 三个组件的版本——runc 是实际运行容器的轻量工具,containerd 负责镜像和容器生命周期。API version 1.41、Go 1.16.12 这些值决定了客户端和服务端能不能混版本用。

[root@localhost ~]# docker version
Client: Docker Engine – Community	#docker客户端
 Version:           20.10.12		#客户端版本号
 API version:       1.41			#API版本号
 Go version:        go1.16.12		#使用的Go版本
 Git commit:        e91ed57		#此次版本提交的commit ID
 Built:             Mon Dec 13 11:45:41 2021
 OS/Arch:           linux/amd64		#系统架构
 Context:           default
 Experimental:      true

Server: Docker Engine – Community	#Server引擎端
 Engine:
  Version:          20.10.12
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.12
  Git commit:       459d0df
  Built:            Mon Dec 13 11:44:05 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:				#containder信息
  Version:          1.4.12		#containder版本
  GitCommit:        7b11cfaabd73bb80907dd23182b9347b4245eb5d
 runc:					#docker引擎,核心组件
  Version:          1.0.2		#版本号
  GitCommit:        v1.0.2-0-g52b36a2
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
[root@localhost ~]# docker info
Client:		#客户端信息
 Context:    default
 Debug Mode: false	#Debug模式管理
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Docker Buildx (Docker Inc., v0.7.1-docker)
  scan: Docker Scan (Docker Inc., v0.12.0)

Server:		#服务端信息
 Containers: 0	#当前宿主机上有多少个容器
  Running: 0	#当前宿主机上运行的容器数量
  Paused: 0	#当前宿主机上暂停的容器数量
  Stopped: 0	#当前宿主机上停止的容器数量
 Images: 0	#当前宿主机上有多少个镜像
 Server Version: 20.10.12
 Storage Driver: overlay2	#使用的存储驱动,overlay2
  Backing Filesystem: xfs	#使用overlay2存储系统格式必须为xfs
  Supports d_type: true		#使用overlay2必须开启d_type
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file	#日志存储类型; json-file会存储在本地,存储在Docker Root Dir指定的路径下
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  #以下支持日志存储的方式
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive		#Swarm未启动
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc	#RunC 是一个轻量级的工具,它是用来运行容器的
 Init Binary: docker-init
 containerd version: 7b11cfaabd73bb80907dd23182b9347b4245eb5d
 runc version: v1.0.2-0-g52b36a2
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 5.4.179-1.el7.elrepo.x86_64	#系统内核版本
 Operating System: CentOS Linux 7 (Core)
 OSType: linux
 Architecture: x86_64
 CPUs: 4
 Total Memory: 7.749GiB
 Name: localhost.localdomain
 ID: 7FLQ:ERSG:BZNW:ERAA:UVWS:QDBW:ADGD:BZ7E:ADTV:WFWU:XKCX:TNPS
 Docker Root Dir: /data/docker		#docker数据存储路径,可更改,建议SSD存储单独挂载
 Debug Mode: false
 Registry: https://index.docker.io/v1/	#默认使用的镜像仓库
 Labels:
 Experimental: false
 Insecure Registries:	#针对于镜像仓库不是https方式访问的,可加入到下面中
  127.0.0.0/8
 Registry Mirrors:
  https://a7ye1cuu.mirror.aliyuncs.com/
 Live Restore Enabled: true	#重启docker进程不会重启正在运行的容器

几个字段值得单独记:Storage Driver: overlay2 配合 Backing Filesystem: xfs,而且必须开启 d_type(Supports d_type: true),否则 overlay2 会出问题;Logging Driver: json-file 表示日志落在本地,路径就是 Docker Root Dir;Insecure Registries 用于非 HTTPS 的私有仓库;Live Restore Enabled: true 让重启 docker 进程不会重启正在运行的容器。检查 d_type 用:

[root@localhost ~]# xfs_info /
docker-xuexi01

镜像与容器的常用命令

搜索、拉取、改标签。不写 tag 默认是 latest;镜像地址、名称、tag 三者完全一致时本地已存在就不会重复拉取;pull 后面不带仓库地址则默认从 Docker Hub 取。

[root@localhost ~]# docker search nginx
docker-xuexi02
[root@localhost ~]# docker pull nginx
docker-xuexi03
[root@localhost ~]# docker tag nginx:latest docker.devops.com/nginx:1.20.1

运行与进入:-it 前台起一个 shell,加 -d 后台运行并只输出容器 ID;之后用 exec 进去,退出后容器仍在跑。

前台启动:
[root@localhost ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    605c77e624dd   6 weeks ago   141MB
[root@localhost ~]# docker run -it nginx:latest /bin/bash
root@f87d99293f63:/#

后台启动:-d参数
[root@localhost ~]# docker container run -it -d nginx:latest /bin/bash
eee23811d4ec6b5a5e8903ae98eb8487c84157da6cbeb6ad14551b45a7dd3b64

[root@localhost ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS     NAMES
eee23811d4ec   nginx:latest   "/docker-entrypoint.…"   2 seconds ago   Up 2 seconds   80/tcp    dazzling_wiles

[root@localhost ~]# docker container exec -it eee23811d4ec /bin/bash
root@eee23811d4ec:/# exit
exit
[root@localhost ~]# docker container ps
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS     NAMES
eee23811d4ec   nginx:latest   "/docker-entrypoint.…"   30 seconds ago   Up 30 seconds   80/tcp    dazzling_wiles

指定容器名称喝容器暴露端口
[root@localhost ~]# docker container run -it -d -p 8080:80 --name nginx-web nginx /bin/bash

浏览器访问宿主机IP+8080端口

其余几个高频动作:

docker ps        # 正在运行的容器
docker ps -a     # 全部容器
docker ps -q     # 运行中容器的 ID
docker ps -qa    # 全部容器的 ID
docker logs -f <CONTAINER ID或NAMES>
docker exec -it <CONTAINER ID或NAMES>
语法:
#本机文件拷贝至容器中
docker cp 本机文件 CONTAINER ID:目标路径

#容器中文件拷贝至本机
docker cp CONTAINER ID:文件名称 本机路径 

指定名称和端口映射用 -p 8080:80 --name nginx-web,浏览器访问宿主机 IP 加 8080 就能看到页面。

Dockerfile 指令

FROM 是必需的,且必须是第一条指令;唯一的例外是 ARG,它可以写在 FROM 前面用来参数化基础镜像,构建时用 --build-arg 参数名=值 覆盖默认值。一个 Dockerfile 可以出现多个 FROM,用于多阶段构建。

FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]
ARG  CODE_VERSION=latest
FROM nginx:${CODE_VERSION}
…
FROM mysql:${CODE_VERSION}
…

MAINTAINER 已弃用,作者信息改用 LABEL:

LABEL <key>=<value> <key>=<value> <key>=<value> ...

RUN 执行 shell 命令,exec 格式会被解析成 JSON,所以不能用单引号:

RUN <command> (shell格式)
RUN ["executable", "parameter1", "parameter2"] (exec格式)

EXPOSE 只是声明容器监听的端口和协议(默认 tcp),它不对外暴露端口,外部要访问必须在 docker run 时用 -p 映射到宿主机:

EXPOSE <port> [<port>/<protocol>...]

CMD 与 ENTRYPOINT 的关系最容易搞错,对应到 K8s 的 deployment 里,CMD 是 ARGS、ENTRYPOINT 是 COMMAND。两者至少要有一个;同时存在时 ENTRYPOINT 是启动命令,CMD 变成它的参数:

CMD ["executable", "parameter1", "parameter2"](exec格式,首选)
CMD ["parameter1","parameter2"](给ENTRYPOINT提供默认参数)
CMD command parameter1 parameter2(shell格式)
ENTRYPOINT ["executable", "parameter1", "parameter2"] (执行格式,首选)
ENTRYPOINT command parameter1 parameter2 (shell格式)

数据、环境、文件与工作目录:

VOLUME ["/data"]
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径>",... "<目标路径>"]
ADD <源路径>... <目标路径>
ADD ["<源路径>",... "<目标路径>"](路径包含空格的必须使用这种格式)
WORKDIR /path/to/workdir
USER <user>[:<group>] or
USER <UID>[:<GID>]

COPY 只复制不解压,路径不存在会自动建目录;ADD 遇到 tar、tar.gz、gzip、bzip2、xz 格式的包会在容器里自动解压,路径含空格必须用 JSON 数组写法。WORKDIR 类似 cd,相对路径是相对上一个工作目录。

构建一次并验证

建目录、放一个 nginx-1.20.2.tar.gz 用来测 ADD 解压,写 Dockerfile:

[root@containers ~]# mkdir dockerbuild
[root@containers ~]# cd dockerbuild/
[root@containers dockerbuild]# ls
nginx-1.20.2.tar.gz	#此文件用来测试ADD指令解压
[root@containers dockerbuild]# vim Dockerfile
ARG CODE_VERSION=latest
FROM nginx:${CODE_VERSION}
LABEL user="example"
RUN mkdir -p /opt/data
EXPOSE 80
CMD ls /opt
ADD ./nginx-1.20.2.tar.gz /opt/data/
USER nginx

不建议同一类操作写成多条指令,镜像层数和体积都会涨。执行构建:

[root@containers dockerbuild]# docker build -t nginx:test .
Sending build context to Docker daemon  1.065MB
Step 1/8 : ARG CODE_VERSION=latest
Step 2/8 : FROM nginx:${CODE_VERSION}
latest: Pulling from library/nginx
a2abf6c4d29d: Pull complete 
a9edb18cadd1: Pull complete 
589b7251471a: Pull complete 
186b1aaa4aa6: Pull complete 
b4df32aa5a72: Pull complete 
a0bcbecc962e: Pull complete 
Digest: sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31
Status: Downloaded newer image for nginx:latest
 ---> 605c77e624dd
Step 3/8 : LABEL user="example"
 ---> Running in ce207d3cd193
Removing intermediate container ce207d3cd193
 ---> 460d8c9fe6b4
Step 4/8 : RUN mkdir -p /opt/data
 ---> Running in abb022bf6313
Removing intermediate container abb022bf6313
 ---> 4351cc5281de
Step 5/8 : EXPOSE 80
 ---> Running in f3ab52ae69f4
Removing intermediate container f3ab52ae69f4
 ---> 467c37a81979
Step 6/8 : CMD ls /opt
 ---> Running in 52268dae069b
Removing intermediate container 52268dae069b
 ---> 6df238b44d40
Step 7/8 : ADD ./nginx-1.20.2.tar.gz /opt/data/
 ---> d22f60131ab7
Step 8/8 : USER nginx
 ---> Running in b5cb554091ac
Removing intermediate container b5cb554091ac
 ---> 8cfb7c300a8c
Successfully built 8cfb7c300a8c
Successfully tagged nginx:test

跑起来看 ADD 是否真的解压了,--rm 让容器退出后自动删除,测试阶段建议都带上:

[root@containers dockerbuild]# docker container run -it --rm nginx:test /bin/bash
nginx@391aa2f4aa2b:/$ ls /opt/data/
nginx-1.20.2
nginx@391aa2f4aa2b:/$ ls /opt/data/nginx-1.20.2/
CHANGES  CHANGES.ru  LICENSE  README  auto  conf  configure  contrib  html  man  src

容易出问题的地方

一、xfs 没开 d_type 就上 overlay2,容器启动报 mount 相关错误,先 xfs_info / 确认再重装。二、把 EXPOSE 当成端口映射,服务起来了外部照样连不上。三、CMD 覆盖 ENTRYPOINT:在 K8s 里改 args 和 command 的效果跟 Dockerfile 里这两条是一一对应的,写清楚谁是指令谁是参数。四、json-file 日志默认不设上限,长期跑能把 Docker Root Dir 所在分区写满,记得配 logging opt 的 max-size。

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容