0%

Docker 使用手册

  • Docker 是一个开源的容器化平台,可以用来打包、分发和运行应用程序。
  • Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何机器上,进而实现快速部署。
  • 本文介绍 Docker 的基本概念、安装方法和常用命令,以及如何使用 Docker 部署应用程序。

安装

Install using script

1
2
curl -fsSL https://get.docker.com -o install-docker.sh
sudo sh install-docker.sh

Install using apt

1
2
sudo apt-get update
sudo apt-get install docker.io

Install from package

  1. 访问这里,找到相应的版本(amd64, arm64…)。
  2. 下载下面这些 deb 包:
    • containerd.io_<version>_<arch>.deb
    • docker-ce-cli_<version>_<arch>.deb
    • docker-ce_<version>_<arch>.deb
    • docker-buildx-plugin_<version>_<arch>.deb
    • docker-compose-plugin_<version>_<arch>.deb
  3. 安装:
    1
    sudo dpkg -i containerd.io_<version>_<arch>.deb docker-ce-cli_<version>_<arch>.deb docker-ce_<version>_<arch>.deb docker-buildx-plugin_<version>_<arch>.deb docker-compose-plugin_<version>_<arch>.deb

安装完成后可以运行下面的命令检查是否安装成功:

1
2
docker version
docker info

Docker 是 C/S 架构,命令行客户端通过 Socket 连接到服务端,而服务端则是一个本机守护进程。运行 Docker 命令的时候需要本机有 Docker 服务,如果使用 docker info 命令时显示无 Server 进程在运行,可以通过下面的命令启动 Docker 服务:

1
2
sudo service docker start
sudo systemctl start docker

Docker 进程使用 Unix Socket 通信,而默认情况下,Unix Socket属于 root 用户,需要 root 权限才能访问。
使用 docker ps 命令若显示 Permission denied,则需要使用 sudo 运行或者通过下面的命令将当前用户加入 docker 用户组:

1
2
3
4
sudo groupadd docker             #添加docker用户组
sudo gpasswd -a $USER docker #将登陆用户加入到docker用户组中
newgrp docker #更新用户组
docker ps #测试docker命令是否可以正常使用

由于Docker Hub被 DNS 污染,国内各镜像源近期发布公告停止Docker Hub的镜像同步,此处推荐使用代理工具,更新 docker.service 文件(/lib/systemd/system/docker.service),添加代理配置:

1
2
3
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"

原理

Docker 是轻量级虚拟化的一种形式,它将运行于同一内核的进程组从环境上彼此隔离,就像运行在不同的机器上一样,为了实现 Docker,内核开发者为内核中的各种全局系统资源(如进程ID、网络协议栈、文件系统等)提供一个间接层,以便每个容器能为这些资源提供各自的实例。

具体来说,Docker 其实就通过 Linux 内核中的 Namespaces 对不同的容器实现了隔离,并通过 clone() 等系统调用创建新进程的同时创建新的 Namespace,从而实现了容器的隔离。

Namespace

Namespace 是 Linux 内核提供的一种系统资源隔离机制,它可以将全局系统资源封装在一个独立的 Namespace 环境中,使得不同 Namespace 中的进程可以拥有各自独立的资源实例,互不干扰,常见的 Namespace 有:

名称 宏定义 隔离内容
IPC CLONE_NEWIPC 实现容器与宿主机、容器与容器之间的IPC隔离,包括信号量、消息队列和共享内存
Network CLONE_NEWNET 提供网络资源的隔离,包括网络设备、IPv4和IPv6协议栈、IP路由表、防火墙、套接字等
Mount CLONE_NEWNS 实现隔离文件系统挂载点,使容器内有独立的挂载文件系统
PID CLONE_NEWPID 实现容器内有独立的进程树(也就意味着每个容器都有自己的PID为1的进程)
User CLONE_NEWUSER 实现用户可将不同的主机用户映射到容器,比如user用户映射到容器内的root用户上
UTS CLONE_NEWUTS 实现容器可以拥有独立的主机名和域名,在网络上可以视为独立的节点
Cgroup CLONE_NEWCGROUP 实现资源的限制(CPU、Memory等等)

clone()

clone() 是 Linux 特有的系统调用,可以通过它来创建一个独立 Namespace 的进程,这个进程被称为子进程。
clone() 创建新进程类似于 fork() 和 vfork(),但可以选择性地共享父进程的资源,通过传递不同的参数可以实现不同的功能,在进程创建期间对步骤的控制相比前者也更加灵活。

1
2
3
4
5
6
#include <sched.h>

// Create a new process
// Return the PID of the child process, or -1 if an error occurred
int clone(int (*func)(void *), void *child_stack, int flags, void *func_arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
标志 设置后的效果
CLONE_CHILD_CLEARTID 当子进程调用 exec()或_exit()时,清除 ctid(从版本 2.6 开始)
CLONE_CHILD_SETTID 将子进程的线程 ID 写入 ctid(从 2.6 版本开始)
CLONE_FILES 父、子进程共享打开文件描述符表
CLONE_FS 父、子进程共享与文件系统相关的属性
CLONE_IO 子进程共享父进程的 I/O 上下文环境(从 2.6.25 版本开始)
CLONE_NEWIPC 子进程获得新的 System V IPC 命名空间(从 2.6.19 开始)
CLONE_NEWNET 子进程获得新的网络命名空间(从 2.4.24 版本开始)
CLONE_NEWNS 子进程获得父进程挂载(mount)命名空间的副本(从 2.4.19 版本开始)
CLONE_NEWPID 子进程获得新的进程 ID 命名空间(从 2.6.23 版本开始)
CLONE_NEWUSER 子进程获得新的用户 ID 命名空间(从 2.6.23 版本开始)
CLONE_NEWUTS 子进程获得新的 UTS(utsname())命名空间(从 2.6.19 版本开始)
CLONE_PARENT 将子进程的父进程置为调用者的父进程(从 2.4 版本开始)
CLONE_PARENT_SETTID 将子进程的线程 ID 写入 ptid(从 2.6 版本开始)
CLONE_PID 标志已废止,仅用于系统启动进程(直至 2.4 版本为止)
CLONE_PTRACE 如果正在跟踪父进程,那么子进程也照此办理
CLONE_SETTLS tls 描述子进程的线程本地存储(从 2.6 开始)
CLONE_SIGHAND 父、子进程共享对信号的处置设置
CLONE_SYSVSEM 父、子进程共享信号量还原(undo)值(从 2.6 版本开始)
CLONE_THREAD 将子进程置于父进程所属的线程组中(从 2.4 开始)
CLONE_UNTRACED 不强制对子进程设置 CLONE_PTRACE(从 2.6 版本开始)
CLONE_VFORK 挂起父进程直至子进程调用 exec()或_exit()
CLONE_VM 父、子进程共享虚拟内存

除了 clone() 系统调用外,Docker 还使用 setns() 系统调用来将进程加入到指定的 Namespace 中,以及 unshare() 系统调用来撤创建的子进程对某个 Namespace 的隔离,具体可以参考这里进行了解。

cgroups

控制组 Cgroups 是一种 Linux 内核功能,用于限制、计量和隔离进程组的资源使用,如 CPU、内存、磁盘 I/O 和网络带宽。Docker 利用控制组来管理和限制容器的资源使用,确保容器之间不会相互干扰,并且能够有效利用系统资源。


命令

镜像

镜像(image)可以理解成一个虚拟机的快照(snapshot),包含要运行的应用程序以及与它相关的所有环境配置和依赖库。镜像是只读的,不可修改,所有的修改都是在容器层面进行的,通过镜像可以创建多个容器实例。

1
2
3
4
5
6
7
8
9
10
docker pull <image_name>[:tag]                               # 拉取镜像
docker images # 查看本地镜像
docker rmi <image_name> # 删除本地镜像
docker build -t <image_name> . # 通过 Dockerfile 构建镜像
docker build -t <image_name> . -no-cache # 不使用缓存构建镜像(重新构建)
docker commit -m "msg" -a "auth" <container_id> <image_name> # 保存容器为镜像
docker save <image_name> > <image_name>.tar # 打包镜像
docker load < <image_name>.tar # 加载镜像
docker tag <image_id> <new_image_name>[:tag] # 重命名镜像
docker image prune # 清理无用镜像

容器

容器(container)是镜像的一个实例,是一个独立运行的应用程序,包含了应用程序的代码、运行时环境、系统工具、系统库和设置。容器是可读可写的,可以在容器内部进行修改,但不会影响到镜像。

1
2
3
4
5
6
7
8
9
10
11
12
13
docker run --name <container_name> <image_name>         # 从镜像创建容器并运行
docker run -p <host_port>:<container_port> <image_name> # 映射容器端口到主机端口
docker cp <container_name>:<container_path> <host_path> # 从容器拷贝文件到主机
docker run -d <image_name> # 后台运行容器
docker start|stop|restart <container_name> # 启动、停止、重启容器
docker rm <container_name> # 删除一个已经停止的容器
docker exec -it <container_name> /bin/bash # 进入一个正在运行的容器
docker inspect <container_name> # 查看容器详细信息
docker logs <container_name> # 查看容器日志
docker ps # 查看正在运行的容器
docker ps -a # 查看所有容器(包括已停止的)
docker top <container_name> # 查看容器内进程
docker container stats <container_name> # 查看容器资源使用情况

仓库

仓库(repository)类似代码仓库,是集中存放镜像文件的场所。全球最大的公开仓库是 Docker Hub,存放了数量庞大的镜像供用户下载。

1
2
3
4
docker login -u <username>                   # 登录 Docker Hub
docker push <username>/<image_name>[:tag] # 推送镜像到 Docker Hub
docker pull <username>/<image_name>[:tag] # 从 Docker Hub 拉取镜像
docker search <image_name> # 搜索镜像

通用

1
2
3
docker --help                               # 查看帮助
docker version # 查看 Docker 版本
docker info # 查看 Docker 详细信息

实践

拉取镜像

1
2
3
4
5
6
➜ docker pull ubuntu:20.04
20.04: Pulling from library/ubuntu
d9802f032d67: Pull complete
Digest: sha256:8e5c4f0285ecbb4ead070431d29b576a530d3166df73ec44affc1cd27555141b
Status: Downloaded newer image for ubuntu:20.04
docker.io/library/ubuntu:20.04

通过镜像创建容器并运行

1
2
3
4
5
6
7
➜ docker run -itd \
--name ubuntu-2004-dev \
--restart always \
ubuntu:20.04 \
/bin/bash
root@d6b193ffbbbc:/# cat /etc/issue # 查看系统版本
Ubuntu 20.04.6 LTS \n \l

-i -t: 保持标准输入打开并分配一个伪终端,确保容器内的 bash 不会因为没有交互而立即退出。
-d: Detached,让容器在后台持续运行。
–restart always: 设置容器自动重启,无论容器是因为进程崩溃退出,还是因为宿主机重启,Docker 守护进程都会自动重新启动该容器。

配置开发环境

1
2
3
root@d6b193ffbbbc:/# apt update && apt-get update
root@d6b193ffbbbc:/# apt install curl gcc g++ cmake gdb vim git iputils-ping -y
root@d6b193ffbbbc:/# curl -LsSf https://astral.sh/uv/install.sh | sh

保存容器为镜像

1
2
➜ docker commit -m "build dev env" -a "xiejunjie" d6b193ffbbbc ubuntu_2004_dev_env
sha256:a111489c0103ba6b8b630dfd4d48a956b41ee54e45fe4f5a5e182533f11c469d

-m 选项用于指定提交的描述信息,-a 选项用于指定提交的作者信息,d6b193ffbbbc 是容器的 ID,ubuntu_2004_dev_env 是新镜像的名称。

查看镜像

1
2
3
4
➜ docker images                                                                           
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu_2004_dev_env latest a111489c0103 5 minutes ago 596MB
ubuntu 20.04 6013ae1a63c2 8 weeks ago 72.8MB

可以看到新创建的镜像 SIZE 比原始的镜像大了很多,这是因为新镜像包含了开发环境的配置。

打包镜像

1
➜ docker save ubuntu_2004_dev_env > ~/HelloDocker/ubuntu_2024_dev_env.tar

加载镜像

1
➜ docker load < ~/HelloDocker/ubuntu_2024_dev_env.tar

上传镜像

1
2
➜ docker tag ubuntu_2004_dev_env xiejunjie/ubuntu_2004_dev_env:latest
➜ docker push xiejunjie/ubuntu_2004_dev_env:latest

参考