Docker 入门

下载

docker 命令

帮助

# 查看 docker 命令的 options 和 command
$ docker --help

# 查看具体 command 的使用方式
$ docker command --help

查看 docker 容器信息

# 查看 docker 所有容器和镜像的数量、docker 使用的执行驱动和存储驱动以及 docker 的基本配置。
# 可以通过这个命令查看 docker 是否安装成功
$ docker info

# 查看正在运行的容器
$ docker ps

# 查看所有的容器,包括已经停止的容器
$ docker ps -a

# 查看最后 x 个容器,不论容器是否停止
$ docker ps -n 3

# 查看容器内的进程,在此约定 CONTAINER 代表 containerId 或者 containerName
$ docker top CONTAINER

# 查看一到多个容器的统计信息,例如:CPU、内存、网络I/O、存储I/O的性能和指标,常用于快速监控
$ docker status CONTAINER1 CONTAINER2 CONTAINER2 ...

# 对容器进行详细的检查,然后返回 json 格式的配置信息,如:名称、命令、网络配置以及很多有用的数据
$ docker inspect CONTAINER
# 可以使用 -f 或者 --format 选定某项结果,其实这个参数支持完整的 GO 语言模版
$ docker inspect --format='' CONTAINER

docker 创建/启动、停止、删除容器

# run 创建、启动一个新的容器
# -i 保证容器中STDIN是开启的,尽管我们没有 attach 到容器中
# -t 为创建的容器分配一个伪 tty 终端
# -i 和 -t 两个参数同时存在,新建的容器才能提供一个交互式 shell
# /bin/bash 执行容器中的 shell
$ docker run -it ubuntu /bin/bash

# 其中 fd2dcda313a4 是容器 id,(hostname)
[email protected]:/#

# 退出 shell,这样容器也会跟着停止
[email protected]:/# exit

# 创建守护式容器(daemonized container)
# -d docker 会将容器放到后台运行,执行完后,他不会像 -it 一样将主机的控制台
# 附着到新的shell会话上,而是仅仅返回了一个容器ID
$ docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

# 为容器命名,这样就不一定非要用容器id去访问容器了,但是容器的命名必须是唯一的
$ docker run --name test_container -i -t centos /bin/bash

# 附着到正在运行容器的会话,可能需要回车才能进入会话
$ docker attach CONTAINER

# 在容器内部运行额外的进程(docker 1.3+)
# 例如进入容器内 shell 会话
$ docker exec -it CONTAINER /bin/bash
# 启动一个新的后台任务
$ docekr exec -d CONTAINER touch /etc/new-config-file

# 停止守护式容器,向 docker 容器进程发送 SIGTERM 信号
$ docker stop CONTAINER

# 如果想快速停止某个容器,还可以直接向 docker 容器进程发送 SIGKILL 信号
$ docker kill CONTAINER

# 重新启动已经停止的 container,docekr 重启的时候会沿用 docker run 时指定的参数来运行。
$ docker start CONTAINER

# 自动重启容器 --restart
# --restart=always 无论容器的退出代码是什么,docker 都会重新启动该容器
$ docker run --restart=always -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
# --restart=on-failure 只有当容器的退出代码为非0值的时候才会自动重启,除此之外还可以接收一个重启次数的参数
$ docker run --restart=on-failure:5 -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

# 删除容器(从这里开始,CONTAINER 代表容器ID/容器名)
$ docker rm CONTAINER

# 删除镜像
$ docker rmi IMAGE_ID

docker 容器日志

# 获取 docker 内部最新的日志
$ docker logs CONTAINER

# -f 滚动输出日志,类似 tail -f
# --tail 10 返回最后10条日志,每次
$ docker logs -f --tail 10 CONTAINER

# docker1.6起,可以控制 docekr 所用的日志驱动,在 docker run 时,使用 --log-driver 进行控制
# --log-driver 默认 json-file 还有其他可用选项,如:
# syslog 该选项禁用 docker logs 命令,并将所有容器的日志输出都重定向到 syslog
# none 该选项也会禁用所有容器中的日志,导致 docker logs 不可用
# 1.8 之后还有 fluentd 日志轮转驱动等等
$ docker run --log-driver="syslog" -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

# 容器最多有三个日志文件,每个日志文件最大500m
$ docker run --log-driver="json-file" --log-opt max-file=3 --log-opt max-size=500m -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

使用 Dockerfile 构建镜像

Best practices for writing Dockerfile

Context

通过Dockerfile的和docker build去构建镜像更具备可重复性、透明性以及幂等性,不推荐通过docker commit构建镜像。

# 创建一个目录用来保存 Dockerfile
$ mkdir contextDir
$ cd contextDir
$ touch Dockerfile

这个目录就是我们的构建环境(context/build context),docker 会把contextDir中的所有文件/文件夹保存到类似/var/lib/docker/tmp/docker-builder833199817目录下,这样在构建镜像的时候就可以直接访问,而对于不在上下文环境中的文件,docker 是访问不到的。

编写 Dockerfile

Dockerfile reference,其中包含了各种命令的使用方法

$ vim Dockerfile

# 指定 base image
FROM ubuntu:14.04
# 指定镜像作者和电子邮件
MAINTAINER zhangqiang "[email protected]"
# RUN指令会在当前镜像中运行指定的命令
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am in your container' \
    >/usr/share/nginx/html/index.html
# 该容器内的应用程序将使用容器的指定端口,但这并不意味着你可以访问容器中运行服务的端口。
# 可以指定多个 EXPOSE 向外部公开多个端口
EXPOSE 80

Dockerfile是由一系列指令和参数组成的。每条指令,如FROM都必须为大写字母,且后面要跟随一个参数。Dockerfile中的指令会按顺序从上到下执行,所以应该根据需要合理安排指令的顺序。

每条指令都会创建一个新的镜像层并commit。docker 执行 Dockerfile 的流程如下:

  • Docker 从基础镜像运行一个容器(因此第一条指令必须是 FROM)
  • 每执行一条指令,对容器作出修改,并执行类似 docker commit 的操作,提交一个新的镜像层。
  • Docker 基于刚提交的镜像运行一个新容器
  • 执行下一条指令,直到所有指令都执行完毕。

从上面可以看出,即便某条指令执行失败了,也会得到一个可以使用的镜像(只要有一条指令执行成功),这样就可以基于镜像启动一个具备交互功能的容器进行调试,查找用户指令失败的原因。

默认情况下,RUN 指令会在shell里使用命令包装器/bin/sh -c来执行,如果是在一个不支持shell的平台上运行或者不希望在shell中运行(比如避免shell字符串篡改),也可以使用exec格式的RUN指令, exec 形式, 与 shell 形式不同,exec 形式不会调用命令 shell,这意味着正常的 shell 处理不会发生。例如,ENTRYPOINT [ "echo", "$HOME" ]不会在$HOME上进行变量替换。如果想要进行shell处理,可以使用shell窗体或直接执行shell,例如:ENTRYPOINT echo "$HOME",而ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=$RUN_ENV"]不会进行$RUN_ENV的变量替换或者将其写在脚本里面,ENTRYPOINT ["./entrypoint.sh"]这样就可以进行变量替换了

RUN ["apt-get", "intsall", "-y", "nginx"]

这种方式,需要使用一个数组来指定要运行的命令和传递给该命令的每个参数。

出于安全原因,docker 并不会自动打开运行服务的端口,而是需要用户在使用 docker run 运行容器的时候指定需要打开的端口。

构建镜像

执行docker build命令时,Dockerfile中的所有指令都会被执行并且提交,并且在该命令成功结束后返回一个新镜像。

$ cd contextDir

# -t 指定仓库和镜像名
# 也可以在创建镜像的过程中指定标签,语法为"镜像名:标签",如果不指定标签则默认为latest
# . 表示上下文环境是当前目录,也可以用绝对路径,docker 默认在指定的 contextDir 的
# 根目录下查找 dockerfile
$ docker build -t="myrepository/myimage:v1" .

Sending build context to Docker daemon  2.048kB
Step 1/5 : FROM ubuntu:14.04
14.04: Pulling from library/ubuntu
72c01b436656: Pull complete 
65584f5f70ee: Pull complete 
dc9874b52952: Pull complete 
86656bbaa6fd: Pull complete 
7fe6916ab382: Pull complete 
Digest: sha256:cb96ec8eb632c873d5130053cf5e2548234e5275d8115a39394289d96c9963a6
Status: Downloaded newer image for ubuntu:14.04
 ---> c32fae490809
Step 2/5 : MAINTAINER zhangqiang "[email protected]"
 ---> Running in 4f9718652c27
Removing intermediate container 4f9718652c27
 ---> 7b453722693d
Step 3/5 : RUN apt-get update && apt-get install -y nginx
 ---> Running in 5cff91f7fd39
Ign http://archive.ubuntu.com trusty InRelease
Get:1 http://security.ubuntu.com trusty-security InRelease [65.9 kB]
Get:2 http://archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Get:3 http://security.ubuntu.com trusty-security/universe Sources [98.6 kB]
...
...
...
Removing intermediate container 5cff91f7fd39
 ---> 342eafd8c1c3
Step 4/5 : RUN echo 'Hi, I am in your container'     >/usr/share/nginx/html/index.html
 ---> Running in f68817f0ba78
Removing intermediate container f68817f0ba78
 ---> b7a7edcb3c66
Step 5/5 : EXPOSE 80
 ---> Running in 12d9168d6c2a
Removing intermediate container 12d9168d6c2a
 ---> c208ff11437a
Successfully built c208ff11437a
Successfully tagged myrepository/myimage:v1

# 可以同时指定多个标签
$ docker build -t="myrepository/myimage:v1" -t "myrepository/myimage:latest" .

# -f 指定 Dockerfile,这里可以起其他的名字,但是必须在上下文环境中
$ docker build -t="myrepository/myimage:v1" -f ./path/other.dockerfile .

# 也可以指定一个 Git 地址(假设Dockerfile在项目的根目录)作为 contextDir,如
$ docker build -t="myrepository/myimage:v1" [email protected]:myrepository/contextDir

如果构建上下文的根目录下存在.dockerignore命名的文件,每一行是一个匹配项(Go语言的filepath),匹配到的文件不会上传到Docker daemon中。与.gitignore比较类似。

Dockerfile 和构建缓存

docker 的每一步的构建过程都会将结果提交为镜像,它会将之前的镜像层视作缓存,即最后一个成功的镜像。如果在step4出错,修改Dockerfile之后再次构建会直接从step4开始。

有时候需要确保构建的过程不能使用缓存,例如已经缓存到了step3。即apt-get update,如果使用缓存,那么docker就不会更新apt的缓存了。可以使用docker run--no-cache忽略Dockerfile的构建缓存

$ docker build -t="myrepository/myimage:v1" --no-cache .

构建缓存的好处是,我们可以实现简单的Dockerfile模版(比如在Dockerfile文件顶部增加包仓库或者更新包,从而尽可能保证缓存命中)。例如在Dockerfile文件顶部使用相同的指令集模版:

FROM ubuntu:14.04
MAINTAINER zhangqiang "[email protected]"
# 设置一个名为REFRESHED_AT的环境变量,表示镜像模板最后的更新时间
ENV REFRESHED_AT 2018-09-14
RUN apt-get -qq update

Docker Compose

Overview of Docker Compose

实战

打包一个日志输出程序的镜像

准备

# 找个 java 镜像
$ docker search java
NAME                                         DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
node                                         Node.js is a JavaScript-based platform for s…   7176                [OK]                
tomcat                                       Apache Tomcat is an open source implementati…   2318                [OK]                
java                                         Java is a concurrent, class-based, and objec…   1960                [OK]                
openjdk                                      OpenJDK is an open-source implementation of …   1562                [OK]  
...

# 提前拉到本地
$ docker pull java:8

# 看下内部情况
$ docker run -it java:8 /bin/bash
# 看下操作系统
[email protected]:/# cat /etc/issue
Debian GNU/Linux 8 \n \l
# 看下系统内核
[email protected]:/# cat /proc/version 
Linux version 4.9.93-linuxkit-aufs ([email protected]) (gcc version 6.4.0 (Alpine 6.4.0) ) #1 SMP Wed Jun 6 16:55:56 UTC 2018
# 看下 java 版本
[email protected]:/# java -version
openjdk version "1.8.0_111"
OpenJDK Runtime Environment (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)
# 好,假装看懂了,退出
[email protected]:/# exit

构建镜像

# 构建上下文环境
$ mkdir log4j_jsonlogs
$ cd log4j_jsonlogs

# 把依赖 jar 放到上下文环境
$ mv /Users/zhangqiang/IdeaProjects/jsonlogs/out/artifacts/jsonlogs_jar/target/jsonlogs.jar .

# 编写 dockerfile
$ vim log4j2_logs.dockerfile
# 指定基础镜像
FROM java:8
# 作者信息
MAINTAINER zhangqiang "[email protected]"
# 在容器内部创建一个文件夹
RUN mkdir /home/log4j
# 将 contextDir 中的 jsonlogs.jar 拷贝到容器的 /home/log4j 目录下
# 注意: source 文件路径是相对于 contextDir 的,并且 docker 只能访问 contextDir 中的文件,如果访问 contextDir
# 之外的文件会出现 COPY failed: Forbidden path outside the build context: ../test (),如果想把 
# contextDir 中的所有文件都拷贝到镜像内可以使用`COPY . /home/log4j`
COPY jsonlogs.jar /home/log4j
# 对外暴露端口
EXPOSE 80
# 当启动容器的时候执行的命令
CMD ["java", "-jar", "/home/log4j/jsonlogs.jar"]

# 构建镜像
$ docker build -t="myrepository/log4j2_logs:v1" -f ./log4j2_logs.dockerfile .

# 查看是否成功打进去了
$ docker run -it myrepository/log4j2_logs:v1 /bin/bash
[email protected]:/home# ls -l /home/log4j/
-rw-r--r-- 1 root root 4335852 Mar 21 05:15 jsonlogs.jar

# 后台运行,并将宿主机的 4000 端口映射到容器暴露的 80 端口  
$ docker run --name log4j2 -d -p 4000:80 --log-driver=json-file myrepository/log4j2_logs:v1

# 查看输出日志
$ docker logs log4j2
{"thread":"main","level":"ERROR","loggerName":"com.rich.LogGenerator","message":"java.lang.IllegalArgumentException: error log\n\tat com.rich.LogGenerator.nestedJsonLogs(LogGenerator.java:30)\n\tat com.rich.LogGenerator.main(LogGenerator.java:17)\n","endOfBatch":false,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger","instant":{"epochSecond":1553157695,"nanoOfSecond":647000000},"threadId":1,"threadPriority":5}
2019-03-21 08:41:35.035 com.rich.LogGenerator ERROR java.lang.IllegalArgumentException: error log
	at com.rich.LogGenerator.nestedJsonLogs(LogGenerator.java:30)
	at com.rich.LogGenerator.main(LogGenerator.java:17)
{"thread":"main","level":"INFO","loggerName":"com.rich.LogGenerator","message":{"girlfriend":"唐xx","province":"山东","name":"张x","age":"24"},"endOfBatch":false,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger","instant":{"epochSecond":1553157695,"nanoOfSecond":787000000},"threadId":1,"threadPriority":5}
...

将 docker image 上传到内网 docker 服务器

# 在公网机器
$ docker pull docker.elastic.co/beats/filebeat:6.4.0

# 查看本地镜像
$ docker images
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
docker.elastic.co/beats/filebeat   6.4.0               9ef2f516cfe7        6 months ago        291MB

# 将本地镜像保存成 tar 文件
$ docker save -o filebeat.tar docker.elastic.co/beats/filebeat:6.4.0

# 将 tar 文件上传到内网服务器

# 加载到内网 docker
$ docker load -i filebeat.tar

# 查看本地 registry 地址,如果修改了 registry 地址记得重启服务才生效
$ vim /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=cgroupfs"],
  "registry-mirrors": [],
  "insecure-registries": ["192.168.51.35:5000"],
  "debug": true,
  "experimental": false
}

# 注意:insecure-registries 不需要执行登录操作,如果有认证的话要先登录
$ docker login $IMAGE_REGISTRY -u $RES_USER -p $RES_PASSWD

# 重新打标签
$ docker tag docker.elastic.co/beats/filebeat:6.4.0 192.168.51.35:5000/filebeat:6.4.0

# 查看本地镜像,可以看到相同 image id 不同库的两个镜像了
$ docker images
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
docker.elastic.co/beats/filebeat   6.4.0               9ef2f516cfe7        6 months ago        291MB
192.168.51.35:5000/filebeat        6.4.0               9ef2f516cfe7        6 months ago        291MB

# 上传到本地 registry
$ docker push 192.168.51.35:5000/filebeat:6.4.0
The push refers to repository [192.168.51.35:5000/filebeat]
be5402f062d8: Pushed 
175d99a09f10: Pushed 
853290f56c1d: Pushed 
4f90788cf2a5: Pushed 
83bce82c7799: Pushed 
9ea2263c9207: Pushed 
1d31b5806ba4: Pushed 
6.4.0: digest: sha256:739453f83c8c707c589d4e28c7a749581ea9d0511fd1ba2e3c97ae015800e103 size: 1786

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,一毛也是爱

打开支付宝扫一扫,即可进行扫码打赏哦