Docker入门指南

2022年 9月 30日 128.9k 0

因我现在的日常工作常会涉及到docker技术,所以希望通过看书和学习总结出一套入门docker的指南,以备之后自己查看和供他人学习之用。本文主要参考《Docker — 从入门到实践》一书进行总结而来。

Docker入门指南

1. 虚拟技术的分类

主要对比的是 Docker 和传统虚拟方式的区别和优劣!

Docker 使用 Google 公司推出的 Go 语言进行开发实现,基于 Linux 内核的 cgroup 和 namespace 技术,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离, 属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初是基于 LXC 实现的,从 0.7 版本开始转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。

  • 传统虚拟技术

Docker入门指南

  • 现代虚拟技术

Docker入门指南

  • 技术对比总结

Docker入门指南

2. 容器技术的优势

我们使用容器化技术,能给我们带来哪些好处呢?

Docker 是个跨时代的开源项目,它彻底释放了计算虚拟化的威力,极大提高了应用的运行效率,降低了云计算资源供应的成本。使用 Docker 可以让应用的部署、测试和分发都变得前所未有的高效和轻松。
Docker入门指南
Docker入门指南

  • 更高效的利用系统资源
    • 无论是应用执行速度、 内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比于传统的虚拟机技术,一个相同配置的主机往往可以运行更多数量的应用。
  • 绝对一致的运行环境
    • 一致的环境保证了一次创建或配置,可以在任意地方正常的运行。从而,保证了持续交付和部署变得简单了。不仅仅开发团队可以方便的使用容器,给客户部署、升级也变得简单了。
  • 更轻松的迁移服务和应用
    • 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。同时,使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。

3. 理解核心概念点

开始学习 Docker 技术之前,我们必须先要了解关于容器技术的几个核心知识点,比如镜像(Image)、容器(Container)、仓库(Repository)。如果能做到融会贯通的话,那么你就基本的理解了 Docker 的整个生命周期。
Docker入门指南

  • 镜像
    • Docker 的镜像就相当于是一个 root 文件系统,且不包含任何动态数据,其内容在构建之后不会被改变。镜像只是一个虚拟的概念,是由多层文件系统分层存储,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层,因此,在构建镜像的时候,需要额外小心。
  • 容器
    • 镜像是静态的定义,容器是镜像运行时的实体,类似于类和实例的关系。容器的实质是进程,而这个进程运行于属于自己的独立的命名空间的隔离环境里。按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。 所有的文件写入操作, 都应该使用数据卷(Volume)或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主或网络存储发生读写,其性能和稳定性更高。
  • 仓库
    • Registry 就是一个集中的存储、分发镜像的服务,可包含多个仓库。通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本,默认为 lastest 标签。

Docker入门指南

4. 容器的快速安装

Docker 划分为社区版(CE)和企业版(EE),前者为免费使用,维护周期为三个月,而后者需要付费才能够使用,主要强调的是安全性与维护周期的时长。

  • [1] Ubuntu 的安装
# 卸载旧版本
$ sudo apt-get remove docker docker-engine docker.io
# 升级apt软件
$ sudo apt-get update
# 使用APT安装
# APT使用HTTPS进行传输的,所以需要安装软件包以及CA证书
$ sudo apt-get install 
     apt-transport-https 
     ca-certificates 
     curl 
     software-properties-common

# 添加软件源的GPG密钥,是为了确认所下载软件包的合法性
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88

# 向source.list中添加Docker软件源
$ sudo add-apt-repository 
     "deb [arch=amd64] https://download.docker.com/linux/ubuntu 
     $(lsb_release -cs) stable"
# 安装Docker-CE服务
$ sudo apt-get update
$ sudo apt-get install docker-ce

# 启动Docker-CE服务
$ sudo systemctl enable docker
$ sudo systemctl start docker

# 建立docker用户组
$ sudo groupadd docker
$ sudo usermod -aG docker $USER

# 测试安装是否成功
$ docker run hello-world
# 卸载docker服务
$ sudo apt-get purge docker-ce

# 删除docker软件目录
$ sudo rm -rf /var/lib/docker
  • [2] CentOS
# 卸载旧版本
$ sudo yum remove docker 
    docker-client docker-client-latest 
    docker-common docker-latest 
    docker-latest-logrotate docker-logrotate 
    docker-selinux docker-engine-selinux 
    docker-engine
# 升级yum软件
$ yum update

# 安装依赖包
$ sudo yum install -y yum-utils 
     device-mapper-persistent-data lvm2

# 添加yum软件源
$ sudo yum-config-manager --add-repo 
     https://download.docker.com/linux/centos/docker-ce.repo
# 安装Docker-CE服务
$ sudo yum makecache fast
$ sudo yum install docker-ce

# 启动Docker-CE服务
$ sudo systemctl enable docker
$ sudo systemctl start docker

# 建立docker用户组
$ sudo groupadd docker
$ sudo usermod -aG docker $USER

# 测试安装是否成功
$ docker run hello-world
# 添加内核参数屏蔽警告信息
$ sudo tee -a /etc/sysctl.conf <<-EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

# 重新加载sysctl.conf配置
$ sudo sysctl -p
# 卸载docker服务
$ sudo apt-get purge docker-ce

# 删除docker软件目录
$ sudo rm -rf /var/lib/docker
  • [3] 使用脚本自动安装
# 官方在测试或开发环境中为了简化安装提供了安装脚本
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh

# 建议使用阿里云镜像(国内的话快很多)
$ curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
  • [4] 配置镜像加速器

注册阿里云账号后,即可在阿里云控制台看到类似如下的页面。
Docker入门指南

5. 核心的要点分析

这里主要参考 卡瓦邦噶 的《Docker(容器)的原理》博客而来!

Docker 里面并没有什么黑魔法,只不过是将一些 Linux 已经有的功能集合在一起,并提供了一个简单的 UI 来创建 “容器”,并没有发明什么新的技术。

  • [1] OCI 容器标准
    • image spec => 容器如何打包
      • 本质上一个 image 就是一个 tar 包
      • layers 就是组成 rootfs 的一些文件
    • runtime spec => 容器如何运行
      • 创建一个 container 然后在 container 中运行进程
      • 容器里面就可以不依赖系统的任何依赖,自己即可独立运行
# 容器的镜像层
$ tar xvf nginx_image.tar -C nginx
......
x fa03658ad40153748b0abbe573db2aaf943049a0749d192a4cfa56f107a80270/
x fa03658ad40153748b0abbe573db2aaf943049a0749d192a4cfa56f107a80270/VERSION
x fa03658ad40153748b0abbe573db2aaf943049a0749d192a4cfa56f107a80270/json
x fa03658ad40153748b0abbe573db2aaf943049a0749d192a4cfa56f107a80270/layer.tar
x manifest.json
x repositories
# 容器的实现和生态 - Docker
oci/runc -> containerd -> moby -> docker

# 容器的实现和生态 - Kubernetes
oci/runc -> cri-o -> kubernetes
oci/runc -> containerd -> cri-containerd -> kubernetes
  • [2] 进程之间的隔离
    • 在容器里面,一个进程只能看到同一个容器下面的其他进程(pid/mount)
    • Namespaces 可以控制进程在 container 中可以看到什么(隔离)
# 手动运行runc来创建我们自己的容器(用busybox演示)
$ mkdir /mycontainer && cd /mycontainer && mkdir rootfs
$ docker export $(docker create busybox) | tar -C rootfs -xvf -

# 创建一个默认的config.json作为配置文件
$ cd /mycontainer
$ runc run mycontainerid

# 查看namespace命名空间(两个终端|一个运行一个观察)
 92518  90576 runc run xyxy
 92524  92521 runc init
 92528  92527 sh

# 查看宿主机这个进程的pidns是什么
$ ps -p 92528 -o pid,pidns
   PID      PIDNS
 92528 4026534092

# 查看对应的ns里面到底有什么
$ ls -l /proc/92528/ns
total 0
lrwxrwxrwx 1 root root 0 Apr  4 23:41 [[cgroup]] -> [[cgroup]]:[4026531835]
lrwxrwxrwx 1 root root 0 Apr  4 23:28 ipc -> ipc:[4026534091]
lrwxrwxrwx 1 root root 0 Apr  4 23:28 mnt -> mnt:[4026534089]
lrwxrwxrwx 1 root root 0 Apr  4 23:27 net -> net:[4026534094]
lrwxrwxrwx 1 root root 0 Apr  4 23:28 pid -> pid:[4026534092]

# 更为详细的信息
$ cinf -namespace 4026534092
  • [3] cgroups 资源分组
    • cgroups 可以控制进程可以使用的资源(资源)
    • 当一个新的 container 创建的时候,容器会为每种资源创建一个 cgroup 来限制容器可以使用的资源
    • 默认情况下的容器是不限制资源的,比如说内存,需要限制的话,可以将限制写入到这个文件里面
# 将系统上的cgroup保存到一个文档当中
$ lscgroup | tee cgroup.b

# 启动一个容器并观察cgroup的变化
$ lscgroup | tee cgroup.a
$ diff cgroup.b cgroup.a
4a5
> net_cls,net_prio:/xyxy
12a14
> pids:/user.slice/user-0.slice/session-c9.scope/xyxy
121a124
> cpuset:/xyxy
129a133
> blkio:/user.slice/user-0.slice/session-c9.scope/xyxy
242a247
> cpu,cpuacct:/user.slice/user-0.slice/session-c9.scope/xyxy
352a358
  • [4] Linux Capabilities
    • 可以在用户有 root 权限的同时,限制 root 使用某些权限
    • 这就是我们在使用容器的时候常见的 CAP_SYS_ADMIN 权限限制了
# 准备好一个带有Libcap的容器
$ docker run -it alpine sh -c 'apk add -U libcap; capsh --print';

# 将这个docker容器导出到runc的rootfs里面
$ docker export 5aad51652320 | tar -C rootfs -xvf -

# 生成一个spec来
$ mkdir test_cap && mv rootfs/ test_cap/ && cd test_cap/
$ runc spec
$ ls
config.json  rootfs

# 进入容器发现,即使是root也无法修改hostname
$ runc run mycap
$ sudo id
uid=0(root) gid=0(root)
$ sudo hostname xintao.local
hostname: sethostname: Operation not permitted
"capabilities": {
  "bounding": [
    "CAP_AUDIT_WRITE",
    "CAP_KILL",
    "CAP_SYS_ADMIN",
    "CAP_NET_BIND_SERVICE"
  ],
  "effective": [
    "CAP_AUDIT_WRITE",
    "CAP_SYS_ADMIN",
    "CAP_KILL",
    "CAP_NET_BIND_SERVICE"
  ],
  "inheritable": [
    "CAP_AUDIT_WRITE",
    "CAP_KILL",
    "CAP_NET_BIND_SERVICE"
  ],
  "permitted": [
    "CAP_AUDIT_WRITE",
    "CAP_SYS_ADMIN",
    "CAP_KILL",
    "CAP_NET_BIND_SERVICE"
  ],
  "ambient": [
    "CAP_AUDIT_WRITE",
    "CAP_KILL",
    "CAP_NET_BIND_SERVICE"
  ]
},
  • [5] 文件系统的隔离(mount namespace)
    • 在容器中只能看到容器里面的文件,而不能看到 host 上面的文件
    • 如果 mount namespace 下面有任何进程修改了 mount table 其他的进程也会受到影响
    • chroot 并没有改变任何 mount table,它只是让进程的 / 看起来就是一个指定的目录
# 启动一个容器可以看到有新的mount的ns
$ sudo cinf | grep mnt
 4026531840  mnt   102     0,1,103,104,112,1000          /sbin/initx
 4026532185  mnt   1       0                             sh
$ sduo cinf -namespace 4026532185
 PID    PPID   NAME  CMD  NTHREADS  CGROUPS STATE
 14013  14003  sh    sh   1         12:blkio:/user.slice/yoyo

# 在host进程上查看mount的info信息
cat /proc/14013/mounts | sort | uniq
cgroup /sys/fs/cgroup/blkio cgroup ro,nosuid,nodev 0 0
cgroup /sys/fs/cgroup/cpu,cpuacct cgroup ro,nosuid,nodev 0 0
cgroup /sys/fs/cgroup/cpuset cgroup ro,nosuid,nodev 0 0
  • [6] User and root
    • 不推荐使用 root 来跑容器
    • config.json 文件中的 User 字段可以指定容器的进程以什么 uid 来运行,默认是 0,即 root
# 字段不是必须的,如果删去,依然是以uisd=0运行
$ id
uid=0(root) gid=0(root)
  • [7] 网络
    • 在网络方面,OCI Runtime Spec 只做了创建和假如 network ns, 其他的工作需要通过 hooks 完成
    • 使用默认的 config.json,就只有一个 loop device,没有 eth0 ,所以也就不能连接到容器外面的网络
# Export the sha256sum for verification.
$ export NETNS_SHA256="8a3a48183ed5182a0619b18f05ef42ba5c4c3e3e499a2e2cb33787bd7fbdaa5c"

# Download and check the sha256sum.
$ curl -fSL "https://github.com/genuinetools/netns/releases/download/v0.5.3/netns-linux-amd64" -o "/usr/local/bin/netns" 
    && echo "${NETNS_SHA256}  /usr/local/bin/netns" | sha256sum -c - 
    && chmod a+x "/usr/local/bin/netns"

$ echo "netns installed!"

# Run it!
$ netns -h

6. 参考的链接地址

送人玫瑰,手有余香!

相关文章

KubeSphere 部署向量数据库 Milvus 实战指南
探索 Kubernetes 持久化存储之 Longhorn 初窥门径
征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
那些年在 Terraform 上吃到的糖和踩过的坑
无需 Kubernetes 测试 Kubernetes 网络实现
Kubernetes v1.31 中的移除和主要变更

发布评论