Docker 学习笔记

Docker 介绍

Docker 是一个虚拟化容器引擎。

开发者可以在 Docker 上部署应用程序或开发环境,然后经过测试后使用 Docker 部署到生产环境。一个主机上可以部署大量的 Docker 实例,而各个实例的启动都非常快。配置好的容器可以简单地分发到别处进行部署。

Docker 的核心组件:

  • Docker 客户端 / 服务器:服务器上运行容器,用客户端或者 RESTful API 可以管理容器
  • Docker 镜像:容器的归档,便于分享、存储
  • 登记处(Registry):Docker 镜像的托管处,镜像的网盘。
  • Docker 容器:运行中的镜像

Docker 基于 Linux 原生容器实现(例如 libcontainer 或 lxc),利用 Linux 内核的名称空间(Namespace)实现文件、进程、网络、硬件资源(利用 cgroups,control group)的隔离。

Docker 安装

自动脚本安装

$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh

下面是手动安装。

配置源仓库

Install Docker Engine on Debian | Docker Documentation

$ sudo apt-get update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
$ echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

安装 Docker 引擎

$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io

Docker 工作流

检查 Docker 引擎:

$ docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Build with BuildKit (Docker Inc., v0.5.1-docker)

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 Server Version: 20.10.7
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 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
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: d71fcd7d8303cbf684402823e425e9dd2e99285d
 runc version: b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: default
 Kernel Version: 4.19.0-17-amd64
 Operating System: Debian GNU/Linux 10 (buster)
 OSType: linux
 Architecture: x86_64
 CPUs: 2
 Total Memory: 3.832GiB
 Name: devhost2
 ID: CMUL:FG32:LLSD:XBRV:WGHU:OW7J:OB3D:HE36:XR5X:DRIY:Q7P2:OLOU
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No swap limit support

创建交互式容器(run -t -i

$ docker run -i -t ubuntu /bin/bash
  • -i 表示开启容器的标准输入

  • -t 表示开启容器的伪 tty 终端

以上保证了能和容器进行交互

  • ubuntu 是 Docker 公司提供的基础镜像
  • /bin/bash 表示在容器中执行 /bin/bash 命令,也即打开终端

使用容器中的终端

在执行了上述命令之后,会看到容器终端中的命令提示符:

root@d4b5b14ba9d4:/# 

容器的主机名就是容器 ID d4b5b14ba9d4

容器的 IP 地址可以在 hosts 中看到:

root@d4b5b14ba9d4:/# cat /etc/hosts 
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2      d4b5b14ba9d4

其中 172.17.0.2 就是容器的 IP。我们同样可以在宿主机看到自己的 IP:

$ ip addr
...
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:9f:7c:70:2e brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:9fff:fe7c:702e/64 scope link 
       valid_lft forever preferred_lft forever

172.17.0.1。容器和宿主可以通过这个子网互通:

$ ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.084 ms

在容器终端输入 exit 退出。

root@d4b5b14ba9d4:/# exit
exit

查看容器(ps

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND             CREATED          STATUS                      PORTS     NAMES
18b7e1c4b646   ubuntu    "/bin/bash"         48 seconds ago   Exited (0) 28 seconds ago             ubuntu_dev

删除容器(rm

删除指定容器

$ docker rm <identifier>

删除所有容器

$ docker rm `docker ps -a -q`

<identifier> 可以是容器 UUID 的一部分(比如 d13d1332cf1af02)也可以是容器名。下同。

容器命名(run --name

使用 --name 参数:

$ docker run -ti --name ubuntu_dev ubuntu /bin/bash

容器启动(start

run 总是会创建新的容器,所以应该用 start 启动旧的容器。

$ docker start <identifier>

容器重启(restart

$ docker restart <identifier>

接入容器(attach

$ docker attach <identifier>

创建守护式(无终端会话)容器(run -d

$ docker run --name daemon_dave -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

读容器日志(logs

跟踪式读取(^C 退出):

$ docker logs <identifier>

读取片段:

-f:打印最新日志

$ docker logs -f <identifier>

--tail 10:打印最后 10 条

$ docker logs --tail 10 <identifier>

-t :加上时间戳

$ docker logs -t <identifier>

查看容器内进程(top

$ docker top 18
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                6201                6181                1                   14:57               pts/0               00:00:00            /bin/bash

在容器内启动进程(exec

$ docker exec -d 18b touch /etc/new_config_file
  • -d 表示后台运行进程

启动交互进程:

$ docker exec -ti 18b /bin/bash

停止容器(stop

$ docker stop <identifier>

自动重启容器(run --restart

$ docker run --restart=<condition> <identifier>

重启条件:

  • always:无论容器退出码是多少都重启
  • on-failure:只在容器退出码为非零值重启
  • on-failure:5:只在容器退出码为非零值重启,最多重启 5 次

端口映射(run -p

$ docker run -d -p 8080:80 <identifier>

可以将宿主的 8080 端口映射到容器的 80 端口。(映射方向是从外到内的)

UDP 端口553:53/udp

全部映射:-P 可以暴露容器的端口(定义在 Dockerfile EXPOSE 中)到宿主的随机端口。

查看容器详情(inspect

$ docker inspect 18
[
    {
        "Id": "18b7e1c4b646b204ecb346570db2cf7273543e59370281df3ef9b9b9d07fd063",
        "Created": "2021-07-17T06:44:36.143947727Z",
        "Path": "/bin/bash",
        "Args": [],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 6201,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2021-07-17T06:57:52.418500032Z",
            "FinishedAt": "2021-07-17T06:57:41.527133515Z"
        },
        "Image": "sha256:c29284518f497b8c5f49933e74e43ca5221e69c8251e780427f7d12f716625ff",
        "ResolvConfPath": "/var/lib/docker/containers/18b7e1c4b646b204ecb346570db2cf7273543e59370281df3ef9b9b9d07fd063/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/18b7e1c4b646b204ecb346570db2cf7273543e59370281df3ef9b9b9d07fd063/hostname",
        "HostsPath": "/var/lib/docker/containers/18b7e1c4b646b204ecb346570db2cf7273543e59370281df3ef9b9b9d07fd063/hosts",
        "LogPath": "/var/lib/docker/containers/18b7e1c4b646b204ecb346570db2cf7273543e59370281df3ef9b9b9d07fd063/18b7e1c4b646b204ecb346570db2cf7273543e59370281df3ef9b9b9d07fd063-json.log",
        "Name": "/ubuntu_dev",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "docker-default",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "CgroupnsMode": "host",
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "KernelMemory": 0,
            "KernelMemoryTCP": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": false,
            "PidsLimit": null,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "MaskedPaths": [
                "/proc/asound",
                "/proc/acpi",
                "/proc/kcore",
                "/proc/keys",
                "/proc/latency_stats",
                "/proc/timer_list",
                "/proc/timer_stats",
                "/proc/sched_debug",
                "/proc/scsi",
                "/sys/firmware"
            ],
            "ReadonlyPaths": [
                "/proc/bus",
                "/proc/fs",
                "/proc/irq",
                "/proc/sys",
                "/proc/sysrq-trigger"
            ]
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/08385fefe81ecd3842deea1a23bf9f5a5c2ce4b56c87cf66eda16b57a1329e00-init/diff:/var/lib/docker/overlay2/05ea6c46c6a2c4e63751e3573bd84c2339f57af8be494c9d62cb3c4196b2cabd/diff",
                "MergedDir": "/var/lib/docker/overlay2/08385fefe81ecd3842deea1a23bf9f5a5c2ce4b56c87cf66eda16b57a1329e00/merged",
                "UpperDir": "/var/lib/docker/overlay2/08385fefe81ecd3842deea1a23bf9f5a5c2ce4b56c87cf66eda16b57a1329e00/diff",
                "WorkDir": "/var/lib/docker/overlay2/08385fefe81ecd3842deea1a23bf9f5a5c2ce4b56c87cf66eda16b57a1329e00/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [],
        "Config": {
            "Hostname": "18b7e1c4b646",
            "Domainname": "",
            "User": "",
            "AttachStdin": true,
            "AttachStdout": true,
            "AttachStderr": true,
            "Tty": true,
            "OpenStdin": true,
            "StdinOnce": true,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/bash"
            ],
            "Image": "ubuntu",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "a9ddd0016f850e03ba96acb32a720b5f4ee7bb5a6fdc8b7e8ecf9722d7313992",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {},
            "SandboxKey": "/var/run/docker/netns/a9ddd0016f85",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "aba8df4c7152cd252ae3fb77c5b360ea3131e97d1b11f474ed9cd884fc58813b",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:02",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "fbec041371667b55aeb73200be03e0a707d4d6df6297c435de5b882aabc1977a",
                    "EndpointID": "aba8df4c7152cd252ae3fb77c5b360ea3131e97d1b11f474ed9cd884fc58813b",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                }
            }
        }
    }
]

可以用 --format='{{.State.Running}}' 这样的参数读取节点信息并格式化输出。

镜像和仓库

Docker 文件系统层

  • 可写容器
  • 镜像

  • 镜像

  • 基础镜像

  • 引导文件系统

一个镜像可以放在另一个镜像的顶部。最底部是镜像栈的基础镜像。Docker 的程序则只能运行在最顶层的读写文件系统。

一个镜像可以依赖于很多镜像。

列出镜像(images

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
ubuntu       latest    c29284518f49   3 days ago   72.8MB

同一个仓库有不同的镜像,通过 tag 来区分。例如:

$ docker run -t -i --name new_container ubuntu:12.04 /bin/bash

表示运行 ubuntu 仓库标签为 12.04 的镜像。本地找不到就会去上层(如 Docker Hub)寻找。

用户仓库和顶层仓库

  • 用户仓库(user repo):普通用户创建,命名为 username/reponame
  • 顶层仓库(top-level repo):由注册处的管理员创建,命名为 reponame

拉取镜像(pull

$ docker pull <repo>

列出仓库的所有镜像(images

$ docker images [repo]

搜索注册处的镜像(search

$ docker search [keywords]

登录到注册处(login

首先需要一个 Docker Hub 账号。去 注册 一个。

执行下列命令登录:

$ docker login

构建镜像(build

可以使用 commit 构建。本文只介绍使用 Dockerfile。

在一个目录创建 Dockerfile 文件:

# Version: 0.0.1
FROM ubuntu:latest
MAINTAINER Pluveto <i@pluvet.com> # 维护者
ENV IMG_UPDATED_AT 20210717 # 环境变量
RUN apt-get update # 执行命令
RUN apt-get install -y nginx
RUN echo 'Hi, I am in your container' > /user/share/nginx/html/index.html
EXPOSE 80

Dockerfile 由一系列命令组成,每条指令都会创建一个新的镜像层并提交# 用于注释。

RUN 也可以用数组形式:

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

执行:

$ docker build -t="username/imagename" .

. 表示使用当前目录的 Dockerfile,也可以用 git 仓库根目录的 Dockerfile:

$ docker build -t="jamtur01/static_web:v1" git@github.com:jamtur01/docker-static_web

可以构建镜像。可以设置一个标签:

$ docker build -t="pluveto/static_web:1.0" .

关闭缓存:

$ docker build --no-cache -t="pluveto/static_web:1.0" .

其它指令:

CMD ["echo", "hello, world!"] # run 时运行的指令。可以被 run 的参数覆盖。
ENTRYPOINT ["/usr/sbin/nginx"] # run 时运行的指令。run 的参数将会拼接到其末尾。
WORKDIR /opt/approot # 设置工作目录
USER nginx [:group] # 设置运行的身份。默认 root
VOLUME ["/opt/project"] # 卷指令,详见后面

查看构建历史(history

$ docker history <identifier>

可以看到镜像的每一层

参考书

  1. 《第一本 Docker 书》- James Turnbull
  2. 《自己动手写 Docker》