Docker 基础

一、Docker 概述

1. Docker 架构与核心概念

docker-architecture

  • Docker uses a client-server architecture. The Docker client talks to the Docker daemon, which does the heavy lifting of building, running, and distributing your Docker containers. The Docker client and daemon can run on the same system, or you can connect a Docker client to a remote Docker daemon. The Docker client and daemon communicate using a REST API, over UNIX sockets or a network interface. Another Docker client is Docker Compose, that lets you work with applications consisting of a set of containers.
  • 镜像:一个只读的文件和文件夹组合,它包含了容器运行时所需要的所有基础文件和配置信息,是容器启动的基础
  • 容器:容器是镜像的运行实体,带有运行时需要的可写文件层。容器运行着真正的应用进程,其本质还是主机上运行的一个进程,但是有自己独立的命名空间隔离和资源限制。容器有初建、运行、停止、暂停和删除五种状态
  • 仓库:用来存储和分发 Docker 镜像,分为公共仓库(如 Docker Hub)和私有仓库(如 Harbor

2. 容器运行时的发展

  • OCI(Open Container Initiative,开放容器标准)规范由 Docker 倡导和提议,联合 CoreOS 等多家厂商,于 2015 年 6 月 22 日发起。OCI 主要包括三个部分:
    • runtime-spec:容器运行时标准,定义了容器运行的配置,环境和生命周期。即如何运行一个容器,如何管理容器的状态和生命周期,如何使用操作系统的底层特性(namespace,cgroup,pivot_root 等)
    • image-spec:容器镜像标准,定义了镜像的格式,配置(包括应用程序的参数,环境信息等),依赖的元数据格式等,简单来说就是对镜像的静态描述
    • distribution-spec:镜像分发标准,即规定了镜像上传和下载的网络交互过程
  • 容器运行时所做的工作(简单来说就是容器的全生命周期管理)
    • 拉取镜像:如果本地没有镜像,从镜像仓库拉取
    • 镜像解压缩:镜像被解压缩到一个 COW(copy-on-write,写时复制)的文件系统上,所有容器层相互堆叠,最终合并成一个文件系统
    • 为容器准备一个挂载点
    • 设置容器:从镜像中获取元数据信息,并对容器进行设置,保证容器能按照期望的方式正常启动,设置过程中还包括来自用户的输入,比如 CMD,ENTRYPOINT 或程序启动的入参等
    • 设置 namespaces:内核为容器设置相关隔离,如文件系统,进程,网络,IPC 等
    • 设置 cgroups:内核为容器设置资源使用限制,如 CPU,memory 等
    • 通过系统调用启动容器
    • 设置 SELinux/AppArmor,即为容器设置相关安全策略
  • 在 Docker 之前,其实早就有了类似的容器技术,就是 LXC(LinuX Containers,Linux 容器)。2008 年发布的 Linux 2.24 内核首次引入了 Cgroups 技术,而在同一时间,Linux 就发布了 LXC
    • LXC 是一个操作系统级的虚拟化方式,对 Cgroups 和 Namespaces 等 Linux 内核特性进行封装,然后提供一个统一的接口,降低用户使用容器技术的门槛
    • 技术层面确实如此,但设计理念却完全不同:LXC 是封装系统的轻量级虚拟机,而 Docker 则是封装应用
    • Docker 依靠统一的内核,从应用的角度出发,每个容器默认只跑一个程序。比如部署 LAMP,那就是 4 个容器,然后再将其组合起来(而不是将环境作为一个整体交付)
  • Docker 在 v1.10 版本之前是直接利用 LXC 来实现容器隔离的。但到了 2014 年,Docker 公司利用 Go 语言开发了新的底层驱动 libcontainer,从而越过 LXC 直接操控 Namespaces 和 Cgroups。如此一来,Docker 便能直接与系统内核打交道,LXC 也就在 Docker 的崛起中被其淘汰
  • 2015 年,Docker 又带头搞出了 OCI 标准。为了推动 OCI 标准的落地,Docker 进一步向前演进自身的架构
    • 先将 libcontainer 独立出来,重构成了 runc 项目,并将其捐献给了 Linux 基金会
      • 由 runc 来负责底层容器的生成和运行,即直接操控 Namespaces 和 Cgroups 等内核特性。官方还特地说明,runc 是一个底层工具,不适合终端用户直接操作,最好配合高层次的容器软件使用(containerd)
    • Docker 继续重构 Docker daemon 子系统,将其中与容器运行时交互的部分抽象为 containerd 项目,并将其捐献给了 CNCF
      • containerd 负责主机上容器的生命周期管理:包括镜像的传输和存储,容器的执行、监管、日志、构建、网络等功能。同样,它也不适合开发人员直接使用,而是嵌入到更大的系统中
      • 实际上,containerd 运行后,内部会创建一个 containerd-shim 进程,与 runc 搭配使用。可以运行一个 Docker 容器后用 ps 看到

二、Docker 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ yum list docker-ce --showduplicates | sort -r
$ yum install docker-ce-<VERSION> docker-ce-cli-<VERSION> containerd.io
$ vim /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"insecure-registries": ["10.4.7.200:88"],
"registry-mirrors": ["https://ung2thfc.mirror.aliyuncs.com","https://registry.docker-cn.com"]
}
$ systemctl start docker

$ sudo usermod -aG docker $USER
  • 配置 Docker 拉取镜像时使用的代理(虚拟机内部需要开启 Clash 的“局域网连接”功能)
1
2
3
4
5
6
7
$ vim /etc/systemd/system/docker.service.d/proxy.conf
[Service]
Environment="HTTP_PROXY=http://10.4.7.1:7897/"
Environment="HTTPS_PROXY=http://10.4.7.1:7897/"
Environment="NO_PROXY=localhost,127.0.0.1,.example.com"
$ systemctl daemon-reload
$ systemctl restart docker

三、Docker 基本操作

1. 镜像操作

  • 镜像标识:[Registry]/[Repository]/[Image]:[Tag]
    • Registry:注册服务器,默认 docker.io,也可以为自建镜像仓库服务器
    • Repository:镜像仓库,默认 library,通常把一组相关联的镜像归为一个镜像仓库
    • Image:镜像名称
    • Tag:镜像标签,默认 latest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 拉取镜像
$ docker pull busybox # docker.io/library/busybox:latest

# 查看镜像
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest f62daa0d2c72 11 months ago 4.26MB

# 镜像打标签
$ docker tag busybox:latest mybusybox:latest

# 删除镜像
$ docker rmi mybusybox

# 从运行中的容器构建镜像
$ docker run --rm --name=busybox -it busybox sh
/ # echo hello > hello.txt
$ docker commit busybox busybox:hello

# 从Dockerfile构建镜像
$ cat Dockerfile
FROM centos:7
COPY nginx.repo /etc/yum.repos.d/nginx.repo
RUN yum install -y nginx
EXPOSE 80
ENV HOST=mynginx
CMD ["nginx","-g","daemon off;"]
$ docker build -t mybusybox .

# 导出镜像
$ docker save mybusybox:latest busybox:latest > busyboxes.tar

# 导入镜像
$ docker load < busyboxes.tar

# 推送镜像到私有仓库
$ docker login 10.4.7.200:88
$ docker tag mybusybox 10.4.7.200:88/public/mybusybox:test
$ docker push 10.4.7.200:88/public/mybusybox:test
  • Dockerfile 常用指令:
指令 描述
FROM Dockerfile 除了注释第一行必须是 FROM,后面跟镜像名称,代表要基于该基础镜像构建容器
RUN RUN 后面跟一个具体的命令
ADD 拷贝本机文件或者远程文件到镜像内
COPY 拷贝本机文件到镜像内
USER 指定容器启动的用户
ENTRYPOINT 容器的启动命令
CMD CMD 为 ENTRYPOINT 指令提供默认参数,也可以单独使用 CMD 指定容器启动参数
ENV 指定容器运行时的环境变量,格式为 key=value
ARG 定义外部变量,构建镜像时可以使用 build-arg= 的格式传递参数用于构建
EXPOSE 指定容器监听的端口,格式为 [port]/tcp 或者 [port]/udp
WORKDIR 为跟在其后的所有 RUN、CMD、ENTRYPOINT、COPY 和 ADD 命令设置工作目录

2. 容器操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 创建容器(未启动)
$ docker create -it --name=busybox busybox

# 启动容器
$ docker start busybox
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5c4bd8cd2a64 busybox "sh" 53 seconds ago Up 1 second busybox

# 创建并启动容器
## -i:Keep STDIN open even if not attached
## -t:Allocate a pseudo-TTY
## -d:Run container in background and print container ID
## -p:Publish a container's port(s) to the host
## -e:Set environment variables
## -v:Bind mount a volume
## -u:Username or UID
## --rm:Automatically remove the container when it exits
$ docker run -it --name=busybox busybox

# 进入容器
$ docker exec -it busybox sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 sh # 当前shell
13 root 0:00 ps aux

# 停止容器(发送SIGTERM信号,如果一段时间后容器未停止,发送SIGKILL信号)
$ docker stop busybox

# 重启容器
$ docker restart busybox

# 删除容器
$ docker rm busybox
$ docker rm -f busybox

# 导出容器
$ docker export busybox > busybox.tar

# 导入容器(导入为镜像)
$ docker import busybox.tar busybox:test