1. 虚拟化和容器技术

  • 虚拟化技术

Type1: 不安装宿主机操作系统,而直接在硬件基础上安装虚拟化管理软件
Type2: 安装宿主机操作系统,在宿主机操作系统之上安装虚拟化管理软件

  • 容器技术

Container 是在宿主机操作系统上使用Cgroups,Namespaceses技术创建出来具有边界的特殊进程。

  • 区别

虚拟化技术会虚拟出多个内核,每个虚拟机拥有一个自己的内核,彼此之间隔离性非常好。并且在创建虚拟机之初就定义好了虚拟机的资源限制,如CPU核心数,内存大小,磁盘大小等。
容器技术是直接运行在宿主机之上的,多个容器共用一个内核,因此隔离效果比较差,但是性能更好。
image.png

2. 容器化的基础

2.1. Chroot

如果需要在一个宿主机上运行多个容器,且容器之间相互个离,那么第一个就需要系统库文件的依赖,对于一个容器而言,需要将其需要的系统文件单独复制出来一份,放到指定目录,并且需要让进程认为这就是根目录,而不是去调用宿主机系统上的库文件。Chroot就是一个切换根目录的方式。

2.2. NameSpaces

为了让多个容器以沙盒的方式在宿主机上运行,就需要提前定义好各个容器能看到的边界。由于各个容器都是直接运行在宿主机系统上,因此需要内核对各个容器的上下文进行修改,让他们看上去是一个独立的操作系统。比如,指定PID为1的进程,指定网卡设备,指定文件系统挂载,指定用户等等。
Linux操作系统内核从底层实现了为各个进程创建独立用户空间的功能,不同用户空间似于一个个独立的虚拟机系统,用户空间内部进程不能感知到其它用户空间中的进程状态。内核提供了六种Namespaces:

UTS hostname and domainname 主机名和域名隔离 内核版本:2.6.19
User 用户隔离。运行进程的用户和组 内核版本:3.8.x
Mount 挂载点隔离。即挂载点隔离,主要指根目录 内核版本:2.4.19
IPC Inter-process-connection 进程间通信隔离。消息队列、共享内存、信号量 内核版本:2.6.19
Pid Process ID PID隔离 内核版本:2.6.24
Net Network 网络隔离。网络设备、协议栈、端口 内核版本:2.6.29

2.3. Cgroups

Namespaceses通过障眼法实现了用户空间的隔离,但是没办法对硬件资源进行限制,当一个容器进行CPU密集型操作时,会消耗掉整个宿主机的CPU资源,进而影响了其它容器的正常运行。
因此在Namespaceses之上,还需要对各个容器实现硬件资源限制,比如CPU,Memory,diskio等等。
Cgroups技术针对进程而言的,在centos7系统上,可以通过以下方式来实现对进程的资源限制:

  1. [root@centos-82 ~]# while :;do :;done &
  2. [1] 2136
  3. [root@centos-82 ~]# pidstat -u -p 2136 2 ## 未加cgroups限制下,跑满单个CPU核心
  4. 11:09:54 AM UID PID %usr %system %guest %CPU CPU Command
  5. 11:09:56 AM 0 2136 99.50 0.00 0.00 99.50 4 bash
  6. 11:09:58 AM 0 2136 100.00 0.00 0.00 100.00 4 bash
  7. 11:10:00 AM 0 2136 100.00 0.00 0.00 100.00 4 bash
  8. [root@centos-82 ~]# mount -t cgroup ## 查看当前cgroups路径
  9. cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
  10. cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
  11. cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
  12. cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
  13. cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
  14. cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
  15. cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
  16. cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
  17. cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
  18. cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
  19. cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
  20. [root@centos-82 ~]# mkdir /sys/fs/cgroup/cpu/loop
  21. [root@centos-82 ~]# cat /sys/fs/cgroup/cpu/loop/cpu.cfs_quota_us
  22. -1
  23. [root@centos-82 ~]# cat /sys/fs/cgroup/cpu/loop/cpu.cfs_period_us
  24. 100000
  25. [root@centos-82 ~]# echo 10000 >/sys/fs/cgroup/cpu/loop/cpu.cfs_quota_us ## CPU时间片限制在10%
  26. [root@centos-82 ~]# echo 2136 > /sys/fs/cgroup/cpu/loop/tasks ## 指定限制的进程PID
  27. [root@centos-82 ~]# pidstat -u -p 2136 2
  28. Linux 3.10.0-862.el7.x86_64 (centos-82) 03/02/2019 _x86_64_ (8 CPU)
  29. 11:16:37 AM UID PID %usr %system %guest %CPU CPU Command
  30. 11:16:39 AM 0 2136 10.50 0.00 0.00 10.50 4 bash
  31. 11:16:41 AM 0 2136 10.00 0.00 0.00 10.00 4 bash
  32. 11:16:43 AM 0 2136 9.50 0.00 0.00 9.50 4 bash
  33. 11:16:45 AM 0 2136 9.95 0.00 0.00 9.95 4 bash

3. Docker架构

3.1. Docker组件

未命名图片.png
Docker服务有三个部分组成,分别是Client,Docker Host,Registry。当创建新的容器时,会向Docker Daemon发送指令,Docker Daemon通过本地镜像文件创建容器,当本地不存在镜像时,将从Registry下载镜像。
Registry由两个部分组成:

  • Repostitory
    • 由特定的docker镜像的所有迭代版本组成一个镜像仓库
    • 一个Registry可以包括多个Repostitory
    • Repostitory包含顶层仓库和用户仓库
      • 顶层仓库: 仓库名:标签, nginx:latest
      • 用户仓库: 用户名/仓库名:标签, heyang/nginx:1.4.2
    • 一个镜像可以有多个标签,如最新版的nginx,可以是nginx:latest,nginx:1.4.2
  • Index
    • 提供用户认证、镜像检索功能

      3.2. Docker镜像和容器

      3.2.1. Docker镜像

      镜像(Image)是一堆只读层(read-only layer)的统一视角。如下图所示:
      522c40256149bad2b471d1a97c2b6bb5.png
      522c40256149bad2b471d1a97c2b6bb5.png
      左边的是多个只读层,他们相互堆叠在一起。除了最下层之外,其它每一层都会有一个指针指向下一层。这些层是Docker内部的实现细节,并且能够在宿主机的文件系统上访问到。
      统一文件系统(union file system,aufs)技术(新版用overlay2)能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。我们可以在图片的右边看到这个视角的形式。
      每一层都包含了当前层的ID,Metadata,Pointer(指向上一层)三层,最底层不包含Pointer。

      3.2.2. Docker容器

      Docker容器包含静止状态和运行状态两种,这两种状态下的层级不一样。
      静态状态的容器仅仅是在镜像状态下增加一个可读写的层级,运行状态中的容器包含了进程和对应的进程空间:
      522c40256149bad2b471d1a97c2b6bb5.png
      522c40256149bad2b471d1a97c2b6bb5.png

4. Docker安装

4.1. Docker安装配置

Docker在2017年以前时使用大版本号+小版本号来名,在2017年之后,采用YY.MM.N-xx格式,如 19.03.1-ce表示2019年3月份的第2个ce版本。

4.1.1. CentOS 7安装docker-ce

4.1.1.1. 内核升级(可选)

CentOS 7 默认的 3.10 版本内核是可以安装 docker-ce 的,但是如果是后续安装 kubernetes ,推荐升级内核到最新的稳定版本。
12-9-软件管理

4.1.1.2. 安装docker-ce

[root@centos-7-51 ~]# uname -r # 确认内核版本,要求大于3.8
5.4.144-1.el7.elrepo.x86_64

[root@centos-7-51 ~]# wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@centos-7-51 ~]# yum repolist

[root@centos-7-51 ~]# yum list docker-ce* --showduplicates  # 通常不安装最新的版本
[root@centos-7-51 ~]# yum install -y docker-ce-19.03.15-3.el7 docker-ce-cli-19.03.15-3.el7
[root@centos-7-51 ~]# cat /etc/docker/daemon.json
{
    "graph": "/data/docker",
    "oom-score-adjust": -1000,
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "32m",
        "max-file": "3"
    },
    "max-concurrent-downloads": 10,
    "max-concurrent-uploads": 10,
    "bip": "172.24.20.1/24",
    "registry-mirrors": ["https://q2gr04ke.mirror.aliyuncs.com"],
    "insecure-registries": ["harbor.ddn.com"],
    "storage-driver": "overlay2",
    "storage-opts": [
        "overlay2.override_kernel_check=true"
    ]
}

4.1.2. ubuntu 1804 安装docker-ce

root@ubuntu-1804-120:~# apt autoremove --purge docker docker-engine docker.io containerd runc
root@ubuntu-1804-120:~# apt update
root@ubuntu-1804-120:~# apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
root@ubuntu-1804-120:~# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
root@ubuntu-1804-120:~# add-apt-repository "deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu $(lsb_release -cs) stable"

root@ubuntu-1804-120:~# apt install -y docker-ce=5:19.03.15~3-0~ubuntu-bionic docker-ce-cli=5:19.03.15~3-0~ubuntu-bionic
root@ubuntu-1804-120:~# vim /etc/docker/daemon.json
{
  "graph": "/data/docker",
  "storage-driver": "overlay2",
  "insecure-registries": ["harbor.ddn.com"],
  "registry-mirrors": ["https://q2gr04ke.mirror.aliyuncs.com"],
  "bip": "172.24.20.1/24",
  "exec-opts": ["native.cgroupdriver=systemd"],
  "live-restore": true,
  "log-opts": {"max-size":"32M", "max-file":"2"}
}
root@ubuntu-1804-120:~# systemctl restart docker
root@ubuntu-1804-120:~# docker info
......
WARNING: No swap limit support

解决上述 docker info 中警告 WARNING: No swap limit support ,可以参考docker官方文档

root@ubuntu-1804-120:~# vim /etc/default/grub # 对GRUB_CMDLINE_LINUX启用swap分区cgroup限制功能
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
root@ubuntu-1804-120:~# update-grub
root@ubuntu-1804-120:~# reboot

4.2. 优化

以下优化参数是从 rancher 官方文档获取

4.2.1. 节点

  • 内核参数 ``` [root@centos-7-51 ~]# vim /etc/sysctl.conf net.bridge.bridge-nf-call-ip6tables=1 net.bridge.bridge-nf-call-iptables=1 net.ipv4.ip_forward=1 net.ipv4.conf.all.forwarding=1 net.ipv4.neigh.default.gc_thresh1=4096 net.ipv4.neigh.default.gc_thresh2=6144 net.ipv4.neigh.default.gc_thresh3=8192 net.ipv4.neigh.default.gc_interval=60 net.ipv4.neigh.default.gc_stale_time=120

参考 https://github.com/prometheus/node_exporter#disabled-by-default

kernel.perf_event_paranoid=-1

sysctls for k8s node config

net.ipv4.tcp_slow_start_after_idle=0 net.core.rmem_max=16777216 fs.inotify.max_user_watches=524288 kernel.softlockup_all_cpu_backtrace=1

kernel.softlockup_panic=0

kernel.watchdog_thresh=30 fs.file-max=2097152 fs.inotify.max_user_instances=8192 fs.inotify.max_queued_events=16384 vm.max_map_count=262144 fs.may_detach_mounts=1 net.core.netdev_max_backlog=16384 net.ipv4.tcp_wmem=4096 12582912 16777216 net.core.wmem_max=16777216 net.core.somaxconn=32768 net.ipv4.ip_forward=1 net.ipv4.tcp_max_syn_backlog=8096 net.ipv4.tcp_rmem=4096 12582912 16777216

net.ipv6.conf.all.disable_ipv6=1 net.ipv6.conf.default.disable_ipv6=1 net.ipv6.conf.lo.disable_ipv6=1

kernel.yama.ptrace_scope=0 vm.swappiness=0

Do not accept source routing

net.ipv4.conf.default.accept_source_route=0 net.ipv4.conf.all.accept_source_route=0

Promote secondary addresses when the primary address is removed

net.ipv4.conf.default.promote_secondaries=1 net.ipv4.conf.all.promote_secondaries=1

Enable hard and soft link protection

fs.protected_hardlinks=1 fs.protected_symlinks=1

see details in https://help.aliyun.com/knowledge_detail/39428.html

net.ipv4.conf.all.rp_filter=0 net.ipv4.conf.default.rp_filter=0 net.ipv4.conf.default.arp_announce = 2 net.ipv4.conf.lo.arp_announce=2 net.ipv4.conf.all.arp_announce=2

see details in https://help.aliyun.com/knowledge_detail/41334.html

net.ipv4.tcp_max_tw_buckets=5000 net.ipv4.tcp_syncookies=1 net.ipv4.tcp_fin_timeout=30 net.ipv4.tcp_synack_retries=2 kernel.sysrq=1

[root@centos-7-51 ~]# sysctl -p


- 打开的文件句柄数

[root@centos-7-51 ~]# vim /etc/security/limits.conf

  • soft nofile 65535
  • hard nofile 65535
    ```
    [root@centos-7-51 ~]# vim /etc/systemd/system.conf  # 修改默认打开的文件数
    DefaultLimitNOFILE=65535
    [root@centos-7-51 ~]# systemctl daemon-reexec
    

    4.2.2. docker

    [root@centos-7-51 ~]# cat /etc/docker/daemon.json
    {
      "graph": "/data/docker",
      "oom-score-adjust": -1000,
      "log-driver": "json-file",
      "log-opts": {
          "max-size": "32m",
          "max-file": "3"
      },
      "max-concurrent-downloads": 10,
      "max-concurrent-uploads": 10,
      "bip": "172.24.20.1/24",
      "registry-mirrors": ["https://q2gr04ke.mirror.aliyuncs.com"],
      "insecure-registries": ["harbor.ddn.com"],
      "storage-driver": "overlay2",
      "storage-opts": [
          "overlay2.override_kernel_check=true"
      ]
    }
    [root@centos-7-51 ~]# systemctl restart docker
    
    [root@centos-7-51 ~]# vim /usr/lib/systemd/system/docker.service # 在[Service]块增加以下两个配置
    OOMScoreAdjust=-1000
    ExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT
    [root@centos-7-51 ~]# systemctl daemon-reload && systemctl restart docker
    

5. Docker相关进程

本章讨论 docker 安装时涉及的软件包,以及启动后涉及的进程。以 CentOS 7 安装 docker-ce 为例。

5.1. 软件包

安装中涉及到的软件包有三个:

  1. docker-ce-cli

docker-ce-cli 是客户端工具,主要提供了 docker 命令,用来连接本地或者远程的 docker 服务端。

[root@centos-7-51 ~]# rpm -ql docker-ce-cli | grep -v man
/usr/bin/docker
/usr/libexec/docker/cli-plugins/docker-app
/usr/libexec/docker/cli-plugins/docker-buildx
/usr/share/bash-completion/completions/docker
  1. docker-ce

docker-ce 提供了三个二进制文件和两个 systemd unit 文件:

[root@centos-7-51 ~]# rpm -ql docker-ce
/usr/bin/docker-init
/usr/bin/docker-proxy
/usr/bin/dockerd
/usr/lib/systemd/system/docker.service
/usr/lib/systemd/system/docker.socket
  • 二进制文件

dockerd : 创建 docker 守护进程的
docker-proxy

  • /usr/lib/systemd/system/docker.service

从service文件能看到,docker.service 强依赖于 docker.socket 和 containerd.service,并且在开机时会在 firewalld 和 containerd 服务之后启动.
docker.service 是通过 dockered二进制文件启动,并且指定了 containerd 的socket 地址
docker.service reload 是通过发送 HUP 到docker.service主进程的

[root@centos-7-51 ~]# cat /usr/lib/systemd/system/docker.service|grep -Ev "#|^$"
[Unit]
BindsTo=containerd.service
After=network-online.target firewalld.service containerd.service
Wants=network-online.target
Requires=docker.socket
[Service]
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT
ExecReload=/bin/kill -s HUP $MAINPID
......
  • /usr/lib/systemd/system/docker.socket ``` [root@centos-7-51 ~]# cat /usr/lib/systemd/system/docker.socket [Unit] Description=Docker Socket for the API PartOf=docker.service

[Socket] ListenStream=/var/run/docker.sock SocketMode=0660 SocketUser=root SocketGroup=docker …….


3. containerd.io

containerd.io 配置文件是`config.toml`,并且包含了 `containerd`、`containerd-shim`和`runc`,`ctr`是containerd的客户端管理工具。

[root@centos-7-51 ~]# rpm -ql containerd.io /etc/containerd/config.toml /usr/bin/containerd /usr/bin/containerd-shim /usr/bin/containerd-shim-runc-v1 /usr/bin/containerd-shim-runc-v2 /usr/bin/ctr /usr/bin/runc /usr/lib/systemd/system/containerd.service ……


- /etc/containerd/config.toml

containerd 配置比 docker 要麻烦的多,但是 docker 本身比较臃肿, kubernetes 正在逐渐抛弃 docker,学会配置 containerd 是必要的。<br />使用`containerd config default` 获取默认配置,相关配置可以参考containerd[官方文档](https://github.com/containerd/cri/blob/master/docs/config.md)。

- 二进制位文件

runc 直接管理容器生命周期的程序,最底层的容器运行时<br />containerd 是containerd服务的守护进程程序,containerd 服务通过 containerd-shim 调用 runc 管理容器生命周期。<br />ctr 是 containerd 的客户端工具

- containerd.service

[Unit] Description=containerd container runtime Documentation=https://containerd.io After=network.target local-fs.target [Service] ExecStartPre=-/sbin/modprobe overlay ExecStart=/usr/bin/containerd Type=notify Delegate=yes KillMode=process Restart=always RestartSec=5 LimitNPROC=infinity LimitCORE=infinity LimitNOFILE=1048576 TasksMax=infinity OOMScoreAdjust=-999 [Install] WantedBy=multi-user.target

<a name="K4jpp"></a>
## 5.2. 进程之间关系
启动两个 docker 容器,并映射端口和存储卷:

[root@centos-7-51 ~]# docker run -d —rm -p 8080:80 -v /tmp/aa:/tmp nginx:latest [root@centos-7-51 ~]# docker run -d —rm -p 8081:80 -v /tmp/bb:/tmp nginx:latest [root@centos-7-51 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2792284fc6e7 nginx:latest “/docker-entrypoint.…” 10 seconds ago Up 9 seconds 0.0.0.0:8081->80/tcp dreamy_leavitt 0b0a76475750 nginx:latest “/docker-entrypoint.…” 18 seconds ago Up 17 seconds 0.0.0.0:8080->80/tcp cool_roentgen

使用 `ps`命令查看进程之间的拓扑关系:<br />在启动容器后,dockerd 服务会启动 docker-proxy 进行端口映射管理,每个需要NAT映射的容器启动一个 docker-proxy<br />每启动一个容器,containerd 服务会创建 containerd-shim 创建容器,containerd-shim会指定使用runc创建容器(/var/run/docker/runtime-runc)

[root@centos-7-51 ~]# ps axf PID TTY STAT TIME COMMAND 11902 ? Ssl 1:18 /usr/bin/dockerd -H fd:// —containerd=/run/containerd/containerd.sock 77366 ? Sl 0:00 _ /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.24.20.2 -container-port 80 77460 ? Sl 0:00 _ /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8081 -container-ip 172.24.20.3 -container-port 80

1596 ? Ssl 6:32 /usr/bin/containerd 77371 ? Sl 0:00 _ containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/0b0a76475750c44a6b30c8dbcc72265164b13e384e843a1126e9cce4c23 77385 ? Ss 0:00 | _ nginx: master process nginx -g daemon off; 77435 ? S 0:00 | _ nginx: worker process 77465 ? Sl 0:00 _ containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/2792284fc6e77f49a48b41e6dc40986fc0897824a5ba397176436fcc128 77480 ? Ss 0:00 _ nginx: master process nginx -g daemon off; 77530 ? S 0:00 _ nginx: worker process

```
[root@centos-7-51 ~]# ps uax|grep containerd
root       1596  0.1  2.4 1089028 49400 ?       Ssl  Sep07   6:34 /usr/bin/containerd
root      11902  0.0  5.2 651988 106032 ?       Ssl  Sep07   1:19 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root      77371  0.0  0.3 709084  7372 ?        Sl   08:11   0:00 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/0b0a76475750c44a6b30c8dbcc72265164b13e384e843a1126e9cce4c2331310 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
root      77465  0.0  0.3 709084  7768 ?        Sl   08:11   0:00 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/2792284fc6e77f49a48b41e6dc40986fc0897824a5ba397176436fcc1282e49e -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc