您当前的位置: 首页 >  kubernetes

科技D人生

暂无认证

  • 0浏览

    0关注

    1550博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Kubernetes 学习总结(25)—— Kubernetes 中的 pod 与容器的区别和联系

科技D人生 发布时间:2022-04-19 04:00:00 ,浏览量:0

前言

容器本可以成为轻量级虚拟机的替代品。但是由于 Docker/OCI 的标准化,最广泛使用的容器形式是每个容器只有一个进程服务。这种方法有很多优点——增加隔离性、简化水平扩展、更高的可重用性等。但是,它也有一个很大的缺点——正常情况下,虚拟(或物理)机器很少只运行一个服务。虽然 Docker 试图提供一些变通方法来创建多服务容器,但 Kubernetes 迈出了更大胆的一步,并选择了一组称为 Pod 的内聚容器作为最小的可部署单元。几年前偶然发现 Kubernetes 时,之前的虚拟机和裸机经验让我很快就了解了 Pod。刚开始接触 Kubernetes 时,学到的第一件事就是每个 Pod 都有一个唯一的 IP 和主机名,并且在同一个 Pod 中,容器可以通过 localhost 相互通信。所以,显而易见,一个 Pod 就像一个微型的服务器。但是,过段时间,你会发现 Pod 中的每个容器都有一个隔离的文件系统,并且从一个容器内部,你看不到在同一 Pod 的其他容器中运行的进程。好吧!也许 Pod 不是一个微型的服务器,而只是一组具有共享网络堆栈的容器。但随后你会了解到,Pod 中的容器可以通过共享内存进行通信!所以,在容器之间,网络命名空间不是唯一可以共享的东西。

探索 Container

OCI 运行时规范并不将容器实现仅限于 Linux 容器,即使用 namespace 和 cgroup 实现的容器。但是,除非另有明确说明,否则本文中的容器一词指的是这种相当传统的形式。设置实验环境(playground)在了解构成容器的 namespace 和 cgroups 之前,让我们快速设置一个实验环境:

$ cat > Vagrantfile {.State.Pid}}' foo)

# Check cgroupfs node for the container main process (4727).
$ cat /proc/${PID}/cgroup
11:freezer:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
10:blkio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
9:rdma:/
8:pids:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
7:devices:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
6:cpuset:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
5:cpu,cpuacct:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
4:memory:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
3:net_cls,net_prio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
2:perf_event:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
1:name=systemd:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
0::/system.slice/containerd.service{.Id}}' foo)

# Check the memory limit.
$ cat /sys/fs/cgroup/memory/docker/${ID}/memory.limit_in_bytes
536870912  # Yay! It's the 512MB we requested!

# See the CPU limits.
ls /sys/fs/cgroup/cpu/docker/${ID} 'ipc:[4026532717]'
lrwxrwxrwx 1 100 101 0 Oct 24 14:05 mnt -> 'mnt:[4026532721]'
lrwxrwxrwx 1 100 101 0 Oct 24 14:05 net -> 'net:[4026532614]'
lrwxrwxrwx 1 100 101 0 Oct 24 14:05 pid -> 'pid:[4026532722]'
lrwxrwxrwx 1 100 101 0 Oct 24 14:05 uts -> 'uts:[4026532716]'

虽然不太容易去注意到,但 httpbin 和 sleep 容器实际上重用了 pause 容器的 net、uts 和 ipc 命名空间!我们可以用 crictl 交叉检测验证:

# Inspect httpbin container.
$ sudo crictl inspect dfb1cd29ab750
{
  ...
  "namespaces": [
    {
      "type": "pid"
    },
    {
      "type": "ipc",
      "path": "/proc/4966/ns/ipc"
    },
    {
      "type": "uts",
      "path": "/proc/4966/ns/uts"
    },
    {
      "type": "mount"
    },
    {
      "type": "network",
      "path": "/proc/4966/ns/net"
    }
  ],
  ...
}

# Inspect sleep container.
$ sudo crictl inspect 097d4fe8a7002
...

我认为上述发现完美的解释了同一个 Pod 中容器具有的能力:

  • 能够互相通信

    • 通过 localhost 和/或

    • 使用 IPC(共享内存,消息队列等)

  • 共享 domain 和 hostname

然而,在看过所有这些命名空间如何在容器之间自由重用之后,我开始怀疑默认边界可以被打破。实际上,在对 Pod API 规范的更深入阅读后发现,将 shareProcessNamespace 标志设置为 true 时,Pod 的容器将拥有四个通用命名空间,而不是默认的三个。但是有一个更令人震惊的发现——hostIPC、hostNetwork 和 hostPID 标志可以使容器使用相应主机的命名空间。有趣的是,CRI API 规范似乎更加灵活。至少在语法上,它允许将 net、pid 和 ipc 命名空间限定为 CONTAINER、POD 或 NODE。因此,可以构建一个 Pod 使其容器无法通过 localhost 相互通信 。

探索 Pod 的 cgroups

Pod 的 cgroups 是什么样的?systemd-cgls 可以很好地可视化 cgroups 层次结构:

$ sudo systemd-cgls
Control group /:
-.slice
├─kubepods
│ ├─burstable
│ │ ├─pod4a8d5c3e-3821-4727-9d20-965febbccfbb
│ │ │ ├─f0e87a93304666766ab139d52f10ff2b8d4a1e6060fc18f74f28e2cb000da8b2
│ │ │ │ └─4966 /pause
│ │ │ ├─dfb1cd29ab750064ae89613cb28963353c3360c2df913995af582aebcc4e85d8
│ │ │ │ ├─5001 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ │ │ │ └─5016 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ │ │ └─097d4fe8a7002d69d6c78899dcf6731d313ce8067ae3f736f252f387582e55ad
│ │ │   └─5035 /bin/sleep 3650d
...

所以,Pod 本身有一个父节点(Node),每个容器也可以单独调整。这符合我的预期,因为在 Pod 清单中,可以为 Pod 中的每个容器单独设置资源限制。此刻,我脑海中的 Pod 看起来是这样的:

利用 Docker 实现 Pod

如果 Pod 的底层实现是一组具有共同 cgroup 父级的半融合(emi-fused)容器,是否可以使用 Docker 生产类似 Pod 的构造?最近我尝试做了一些类似的事情来让多个容器监听同一个套接字,我知道 Docker 可以通过 docker run —network container:语法来创建一个可以使用已存在的网络命名空间容器。但我也知道 OCI 运行时规范只定义了 create 和 start 命令。因此,当你使用 docker exec在现有容器中执行命令时,实际上是在运行(即 create 然后 start)一个全新的容器,该容器恰好重用了目标容器的所有命名空间(证明 1[1] 和 2[2])。这让我非常有信心可以使用标准 Docker 命令生成 Pod。我们可以使用仅仅安装了 Docker 的机器作为实验环境。但是这里我会使用一个额外的包来简化使用 cgroups:

$ sudo apt-get install cgroup-tools

首先,让我们配置一个父 cgroup 条目。为了简洁起见,我将仅使用 CPU 和内存控制器:

sudo cgcreate -g cpu,memory:/pod-foo

# Check if the corresponding folders were created:
ls -l /sys/fs/cgroup/cpu/pod-foo/
ls -l /sys/fs/cgroup/memory/pod-foo/

然后我们创建一个沙盒容器:

$ docker run -d --rm \
  --name foo_sandbox \
  --cgroup-parent /pod-foo \
  --ipc 'shareable' \
  alpine sleep infinity

最后,让我们启动重用沙盒容器命名空间的实际容器:

# app (httpbin)
$ docker run -d --rm \
  --name app \
  --cgroup-parent /pod-foo \
  --network container:foo_sandbox \
  --ipc container:foo_sandbox \
  kennethreitz/httpbin

# sidecar (sleep)
$ docker run -d --rm \
  --name sidecar \
  --cgroup-parent /pod-foo \
  --network container:foo_sandbox \
  --ipc container:foo_sandbox \
  curlimages/curl sleep 365d

你注意到我省略了哪个命名空间吗?没错,我不能在容器之间共享 uts 命名空间。似乎目前在 docker run 命令中没法实现。嗯,是有点遗憾。但是除开 uts 命名空间之外,它是成功的! cgroups 看上去很像 Kubernetes 创建的:

$ sudo systemd-cgls memory
Controller memory; Control group /:
├─pod-foo
│ ├─488d76cade5422b57ab59116f422d8483d435a8449ceda0c9a1888ea774acac7
│ │ ├─27865 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ │ └─27880 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ ├─9166a87f9a96a954b10ec012104366da9f1f6680387ef423ee197c61d37f39d7
│ │ └─27977 sleep 365d
│ └─c7b0ec46b16b52c5e1c447b77d67d44d16d78f9a3f93eaeb3a86aa95e08e28b6
│   └─27743 sleep infinity

全局命名空间列表看上去也很相似:

$ sudo lsns
        NS TYPE   NPROCS   PID USER    COMMAND
...
4026532157 mnt         1 27743 root    sleep infinity
4026532158 uts         1 27743 root    sleep infinity
4026532159 ipc         4 27743 root    sleep infinity
4026532160 pid         1 27743 root    sleep infinity
4026532162 net         4 27743 root    sleep infinity
4026532218 mnt         2 27865 root    /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
4026532219 uts         2 27865 root    /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
4026532220 pid         2 27865 root    /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
4026532221 mnt         1 27977 _apt    sleep 365d
4026532222 uts         1 27977 _apt    sleep 365d
4026532223 pid         1 27977 _apt    sleep 365d

httpbin 和 sidecar 容器看上去共享了 ipc 和 net 命名空间:

# app container
$ sudo ls -l /proc/27865/ns
lrwxrwxrwx 1 root root 0 Oct 28 07:56 ipc -> 'ipc:[4026532159]'
lrwxrwxrwx 1 root root 0 Oct 28 07:56 mnt -> 'mnt:[4026532218]'
lrwxrwxrwx 1 root root 0 Oct 28 07:56 net -> 'net:[4026532162]'
lrwxrwxrwx 1 root root 0 Oct 28 07:56 pid -> 'pid:[4026532220]'
lrwxrwxrwx 1 root root 0 Oct 28 07:56 uts -> 'uts:[4026532219]'

# sidecar container
$ sudo ls -l /proc/27977/ns
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 ipc -> 'ipc:[4026532159]'
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 mnt -> 'mnt:[4026532221]'
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 net -> 'net:[4026532162]'
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 pid -> 'pid:[4026532223]'
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 uts -> 'uts:[4026532222]'
总结

Container 和 Pod 是相似的。在底层,它们主要依赖 Linux 命名空间和 cgroup。但是,Pod 不仅仅是一组容器。Pod 是一个自给自足的高级构造。所有 Pod 的容器都运行在同一台机器(集群节点)上,它们的生命周期是同步的,并且通过削弱隔离性来简化容器间的通信。这使得 Pod 更接近于传统的 VM,带回了熟悉的部署模式,如 sidecar 或反向代理。

关注
打赏
1662604032
查看更多评论
立即登录/注册

微信扫码登录

0.0447s