Kube-#01.1 先从Docker 容器开始
- MetricVoid
- 8月 7, 2021
- 技术笔记
- 0 Comments
Kubernetes上的各个应用都在容器中运行,而大部分容器都使用Docker构建。(其他容器环境还包括Linux Container (LXC) 之类,但是在Kubernetes中运用较少。Docker部分上是基于LXC开发的)
容器和虚拟机的设计目的是差不多:他们都能给其中的应用程序提供一个虚拟的操作系统和运行环境。但在实现方法上两者较为不同。
- 虚拟机对硬件的模拟程度更深,会给虚拟机分配一定数量的CPU、内存,硬盘资源。有些虚拟机Hypervisor还会虚拟化CPU的型号和指令集以达成更好的可移植性。
- 因为如此,虚拟机的资源消耗较大,性能损失也较大。与之相对的,虚拟机有着更高的灵活性,可以实现一些底层的操作,包括写CPU寄存器,更改系统内核设置等。
- 因为有着虚拟显卡,虚拟机可以提供图形界面。
- 虚拟机可以运行和宿主机不一样的操作系统。例如,在Linux系统中运行的虚拟机可以运行Windows虚拟机。
- 虚拟机可以运行和宿主机不一样的CPU架构。例如,x86的处理器可以运行ARM的虚拟机。
- 一般应用场景下,虚拟机内部会运行多个进程,多个软件,甚至可以当作完整的另一台计算机来使用。
- 容器不虚拟出硬件资源,而是和宿主机共享操作系统内核,包括CPU、内存等。容器在内核之上定义了一个应用层。
- 容器的资源消耗较小,其消耗的资源基本上等于内部进程消耗的资源。一个容器甚至可以只占几百KB内存。
- 如果在容器内部查看CPU和内存等系统资源的话,会发现CPU、内存数量等和宿主机是一样的。这是因为CPU数量,内存信息等是通过内核获取的,而容器和宿主机使用同一个内核。
- 在创建和运行Docker容器时,我们可以对容器可使用的资源数量进行限制,包括CPU时间限制和内存限制。还可以通过宿主机上容器线程的CPU关联性(CPU Affinity) 限制容器只能使用某几个CPU核心。但是因为是同一个内核,
/proc/cpuinfo
和/proc/meminfo
显示的总会是宿主机的CPU和内存数量。 - CPU限制不一定需要是整数。比如可以限制容器只能使用1.5个CPU。这是利用“CPU时间”计算的。比如一个8核的CPU在1000毫秒内有8000毫秒·CPU单位,而1.5个CPU则代表容器可以使用其中的1500个CPU单位。毫秒·CPU单位可被称为mCPU。例如,一个八核的处理器可以提供8000mCPU。一个容器可以分配到其中的1500mCPU
- 在创建和运行Docker容器时,我们可以对容器可使用的资源数量进行限制,包括CPU时间限制和内存限制。还可以通过宿主机上容器线程的CPU关联性(CPU Affinity) 限制容器只能使用某几个CPU核心。但是因为是同一个内核,
- 理论上,容器可以使用图形界面:在Linux环境下,把宿主机的X Server直通给容器,就可以让容器产生图形界面。但是事实上没人这么做。
- 容器可以使用GPU进行计算:Nvidia提供的Docker CUDA SDK,可以将宿主机上的CUDA驱动传递给Docker容器,容器便可以使用CUDA函数进行GPU计算。
- 一般一个容器只会运行一个进程(Process),但是这个进程可以有很多的线程(Thread)。虚拟机从外部关闭时,会给内部系统发一个ACPI的关机信号,而Docker从外部关闭时,会给主线程发送
SIGTERM
。 - 容器不能直接运行在不同的操作系统上。比如,Windows系统无法原生运行Linux的容器。
- Windows上的Docker环境,Docker Desktop,是先使用WSL2或者Hyper-V创建了一个Linux的虚拟机,再在这个虚拟机里跑容器的。
- Linux上暂时无法运行Windows容器。主要是因为这需要在Linux上运行一个Windows虚拟机,这会牵涉到授权和证书的问题,而Windows不是免费的,所以没有这方面的工作。
- 容器不能跑和宿主机不同的CPU架构。
实践 1 总之先整个Docker玩一玩
在进一步了解Docker底层原理之前,不如先整一个来玩一玩
Windows系统
Step 1. 安装WSL 2
Windows系统首先需要安装一个WSL 2 (Windows Subsystem for Linux 2)。如上所述 Linux容器无法直接在Windows下运行。需要先安装WSL,再升级到WSL2。
首先确认你的操作系统版本和系统架构,至少需要 Windows 10 1903 Build 18362
,且ARM版本的Windows暂时不支持Docker Desktop。操作系统版本过低的话可以使用升级助手来更新。
如果你不清楚自己的系统是什么架构,可以在命令行中使用 systeminfo | find "System Type"
来确认。大部分的电脑会显示x86-based(32位系统)或x64-based(64位系统)。显示ARM字样的话就是ARM架构。
然后启动一个管理员权限的命令行,执行这个命令,启用WSL功能。
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
然后使用这个命令,启用Windows的虚拟机功能。注意:启用这个功能可能会导致VirtualBox等虚拟机无法使用。新版本的VMWare和Hyper-V不受影响。
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
这时再安装WSL 2 的内核更新包
然后再将WSL的默认版本设为2:
wsl --set-default-version 2
WSL 2 就安装完毕了。
Step 2. 安装Docker Desktop
在这里下载Docker Desktop for Windows: Docker Desktop for Windows by Docker | Docker Hub。点击右侧的 “Get Docker” 即可下载安装包。
(可选)在安装完成后,可以进入Docker Desktop的设置,将Docker Hub源改为中国区的镜像。点击右上角的齿轮,选择Docker Engine,在registry-mirrors中可以加入以下镜像:
- 有稳定外网连接时,不建议设置额外的镜像源。
- https://docker.mirrors.ustc.edu.cn :中科大镜像
- http://hub-mirror.c.163.com:网易镜像。注意这个没有TLS。
- 也可以去这个地方 获得一个阿里云的镜像源:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 这个不是镜像地址 不要直接复制!
更改完成后,选择Apply&Restart,重启Docker Engine。
Linux系统
以下仅提供在常见发行版上安装最新稳定版的操作。具体不同发行版的安装方法可见 Install Docker Engine | Docker Documentation
Debian / Ubuntu
首先添加Docker的GPG公钥和APT源。Docker 的APT源是使用HTTPS的,所以还得安装一些依赖库。
sudo apt-get update sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
然后更新软件源列表,安装
sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io
CentOS / RHEL
使用yum-utils添加Docker的yum源,然后直接安装
sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io
Arch Linux
简单暴力的优雅美学
pacman -S docker
Docker镜像的命名规则
Docker镜像的名字包含了两个部分,REPOSITORY
和 TAG
。REPOSITORY一般指明了这个镜像的内容,而TAG用来区分不同的版本。TAG省略时默认为latest
,代表最新版。REPOSITORY
中可能还包含了一个镜像的registry,如果不指定的话则默认为Docker Hub(docker.io)。看下面的几个例子:
python
:REPOSITORY为python
,tag被省略了,默认为latest
。通过查阅Docker Hubs的文档,我们可以看到latest
在Linux环境下,目前等同于3.9.6-buster
(撰写本文时,Python最新版本为3.9.6),即这是一个基于Debian Buster的Python 3.9.6镜像。python:3.9.5-alpine
:顾名思义,这是一个基于Alpine Linux的Python 3.9.5的镜像。REPOSITORY为python
,TAG部分为3.9.5-alpine
mcr.microsoft.com/mssql/server:2019-latest
:这个镜像的REPOSITORY部分为mcr.microsoft.com/mssql/server
。注意它的REPOSITORY有一个mcr.microsoft.com/
的前缀,意味着这个镜像并不处在Docker Hub(docker.io)上,而是处在Microsoft Container Registry (MCR) (mcr.microsoft.com)上。在使用这个镜像的时候,Docker不会访问docker.io,而是会从mcr.microsoft.com下载这个镜像。TAG部分为2019-latest
。这个镜像是Microsoft SQL Server 2019。Microsoft SQL Server by Microsoft | Docker Hub。同样,如果你构建了一个镜像,并把它命名为mcr.microsoft.com/xxxx
,那么在使用docker push
将镜像发布到registry上的时候,docker
命令行软件也不会联系docker.io,而是直接联系mcr.microsoft.com并尝试发布镜像。当然了是push不上去的,需要权限以及安全审查,开源审查之类,这个onboarding烦得要死。
常用的Docker镜像介绍
在构建我们自己的Docker镜像时,需要有一个“基础镜像”作为起点。他们一般提供了一个操作系统或者某个编程语言的运行环境。当然基础镜像也是可以拿来运行的。常见的一些基础镜像如下。
首先介绍一下:C语言有几种基础运行库:glibc,musl和uclibc。glibc是最常用的,功能也最全,大部分Linux大型版都基于它,当然体积也比较大。musl的体积较少,占用资源少,常见于路由器之类的嵌入式Linux设备。uclibc也是一个体积较小的C语言基本库,u代表micro,我没用过。
alpine
:Alpine Linux,以小而著称。镜像只有5M左右的大小,而且有着包管理器apk
(Alpine Package Keeper),可以方便地安装一些库和软件。使用的C语言基本库是muslbusybox
:Busybox是一套Linux工具箱,是为嵌入式Linux和极少量资源而设计的。它在一个软件里提供了Linux基本所有的基础功能,包括cat, grep等,甚至包括wget。他有使用musl,glibc和uclibc的版本,最小的镜像只有700多KB,使用glibc的也不过2M左右ubuntu
:Ubuntu,没什么好说的。debian
:Debian,没什么好说的。python
:包含了Python,pip之类,有基于Debian, Alpine Linux和Windows Server Core的版本。基本上覆盖了Python 2 – Python 3的所有版本。- 注意,如果需要使用numpy,pandas之类的话,不建议选择Alpine Linux的版本。这些库包含C语言的部分在Ubuntu之类平台上有预编译好的版本,而在Alpine上构建的时候,需要下载gcc并重新编译。不仅构建时间长,而且构建出来的镜像可能比Debian版本差不多大,甚至还要大(因为下载了很多native的依赖库比如libgomp之类,而这些依赖库在musl上比在glibc上要大一些)。
openjdk
:JDK,从Java 6 开始全都有。有基于Debian和Windows Server Core的版本- 没有基于Alpine的版本,因为JVM用的是glibc,而且没有musl版。想在Alpine上用的话可以用
zulu-openjdk-alpine
- 一般使用的时候会用jlink构建出一个JRE来再用。openjdk本身镜像接近200M。
- 没有基于Alpine的版本,因为JVM用的是glibc,而且没有musl版。想在Alpine上用的话可以用
跑一个镜像试一试
下面我们可以运行一个镜像,顺便认识一下“容器和宿主机共享内核”的本质。
首先来确认一下系统的内核和CPU数量。在Windows下的话,别忘了Docker Desktop是在WSL 2的虚拟机里跑容器的。先使用WSL进入Docker Desktop跑容器用的WSL 2 虚拟机。docker-desktop
是Docker Desktop自动部署的一个WSL 2发行版,用来跑容器,一般不需要自己去访问,但我们可以切一个命令行进去。用Linux的直接跳到下一步
wsl -d docker-desktop
然后我们看一看/proc
,确认一下系统的CPU和内存。运行这两个指令(一次复制粘贴一行,分开执行)
cat /proc/cpuinfo | grep processor cat /proc/meminfo | grep MemTotal
可以看到,这个宿主机拥有4核CPU(包括超线程),8148284KB(大概8G)的内存。然后我们运行一个busybox
的Docker容器,看一看容器里面看到的系统资源是多少。
如果你是Windows用户的话,现在可以退出这个WSL的命令行了。使用exit
退出docker-desktop
的命令行。
在主机上执行以下命令。
docker run -it busybox /bin/sh
这个命令指的是,从名为busybox
(也就是busybox:latest
)的镜像创建一个Docker容器,并给他分配一个TTY(-t
选项),将当前终端的输入/输出连接到这个容器(-i
,代表交互式,interactive),并在这个容器里运行/bin/sh
(也就是打开一个shell)。
镜像是包含自己的启动指令的,比如你构建的镜像,启动指令可能是打开你自己的应用程序。这里加了/bin/sh
进行了一个覆盖,也就是指忽略镜像定义的启动指令,而是运行/bin/sh
。(虽然busybox镜像本身定义的启动指令就是/bin/sh
,但自己指定也有好处 – 知道自己在干什么。)
然后我们就来到了这个新的shell里。这个shell就是这个容器的终端了。执行uname -a,再执行以下上面两行命令看一看
可以看到,容器内部看到的CPU和内存 与主机没有区别,uname -a显示的也是宿主机的内核名称。现在我们在主机上执行docker ps
,查看一下这个容器的信息
可以看到,这个容器正在运行,它的ID是bc591211e876
,使用的镜像是busybox
,执行的指令是/bin/sh
,已经启动了三分钟。NAMES
下的laughing_wozniak
是Docker为这个容器自动生成的一个名字(因为我们在使用docker run
的时候没有给容器起名)。
接下来,在容器内部执行hostname
,看看容器的主机名
可以看到,容器内部看到的自己的主机名是和容器的ID一样的。Docker自动设置了/etc/hostname
文件。
接下来我们试一试限制容器的资源。首先,在容器内执行exit
,退出这个容器的命令行。再使用docker rm bc591211e876
或者docker rm laughing_wozniak
来删除这个容器。
启动一个新的容器,这次加上CPU和内存限制。
docker run --cpuset-cpus "0-1" -m 512M -it busybox /bin/sh
这指的是,容器只能使用CPU的0和1核心,而且内存限制为512M。再在容器里看一看/proc
可以看到,即使已经限制了容器的CPU核心和内存使用,通过/proc
看到的CPU和内存信息仍然是和宿主机一样的。这还是因为共享内核。
再试试别的…
下面就可以试试看别的容器镜像了,比如可以使用docker run -it python /bin/sh
来启动一个预装了Python运行时的容器,然后试试看pip和python REPL。
关于启动/终止容器,一些常用的Docker命令
注:这里只介绍极少量的命令。今后会介绍更多。
docker run
:运行容器。可以配置终端、网络端口映射、CPU内存限制等,具体 –helpdocker run python
:启动一个python容器,让它在后台运行,不给它分配TTY。很明显这指令没有什么用。
docker ps
:查看运行中的容器,列出他们的CONTAINER ID和NAMEdocker ps -a
:查看所有容器,包括运行中的和停止的。docker stop <CONTAINER ID/NAME>
:给这个容器中的主进程(启动进程)发送一个SIGTERM
信号,让他停止。docker stop <CONTAINER ID/NAME> -f
:给这个容器中的主进程发送一个SIGKILL
,强制终止。docker rm <CONTAINER ID/NAME>
:移除一个容器。这个容器必须已经停止了。docker images
:查看本地保存的docker镜像,包括你自己构建的和下载的。docker rmi <IMAGE ID/REPOSITRY:TAG>
:删除一个本地的docker镜像。这个镜像必须没有在被任何容器使用。