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 的一部分(比如 d13
,d1332cf1af02
)也可以是容器名。下同。
容器命名(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>
可以看到镜像的每一层
参考书
- 《第一本 Docker 书》- James Turnbull
- 《自己动手写 Docker》