因我现在的日常工作常会涉及到
docker
技术,所以希望通过看书和学习总结出一套入门docker
的指南,以备之后自己查看和供他人学习之用。本文主要参考《Docker — 从入门到实践》一书进行总结而来。
1. 虚拟技术的分类
主要对比的是 Docker 和传统虚拟方式的区别和优劣!
Docker
使用 Google
公司推出的 Go
语言进行开发实现,基于 Linux
内核的 cgroup
和 namespace
技术,以及 AUFS
类的 Union FS
等技术,对进程进行封装隔离, 属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初是基于 LXC
实现的,从 0.7
版本开始转而使用自行开发的 libcontainer
,从 1.11
开始,则进一步演进为使用 runC
和 containerd
。
- 传统虚拟技术
- 现代虚拟技术
- 技术对比总结
2. 容器技术的优势
我们使用容器化技术,能给我们带来哪些好处呢?
Docker
是个跨时代的开源项目,它彻底释放了计算虚拟化的威力,极大提高了应用的运行效率,降低了云计算资源供应的成本。使用 Docker
可以让应用的部署、测试和分发都变得前所未有的高效和轻松。
- 更高效的利用系统资源
- 无论是应用执行速度、 内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比于传统的虚拟机技术,一个相同配置的主机往往可以运行更多数量的应用。
- 绝对一致的运行环境
- 一致的环境保证了一次创建或配置,可以在任意地方正常的运行。从而,保证了持续交付和部署变得简单了。不仅仅开发团队可以方便的使用容器,给客户部署、升级也变得简单了。
- 更轻松的迁移服务和应用
- 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。同时,使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。
3. 理解核心概念点
开始学习 Docker
技术之前,我们必须先要了解关于容器技术的几个核心知识点,比如镜像(Image
)、容器(Container
)、仓库(Repository
)。如果能做到融会贯通的话,那么你就基本的理解了 Docker
的整个生命周期。
- 镜像
Docker
的镜像就相当于是一个root
文件系统,且不包含任何动态数据,其内容在构建之后不会被改变。镜像只是一个虚拟的概念,是由多层文件系统分层存储,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层,因此,在构建镜像的时候,需要额外小心。
- 容器
- 镜像是静态的定义,容器是镜像运行时的实体,类似于类和实例的关系。容器的实质是进程,而这个进程运行于属于自己的独立的命名空间的隔离环境里。按照
Docker
最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。 所有的文件写入操作, 都应该使用数据卷(Volume
)或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主或网络存储发生读写,其性能和稳定性更高。
- 镜像是静态的定义,容器是镜像运行时的实体,类似于类和实例的关系。容器的实质是进程,而这个进程运行于属于自己的独立的命名空间的隔离环境里。按照
- 仓库
Registry
就是一个集中的存储、分发镜像的服务,可包含多个仓库。通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本,默认为lastest
标签。
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] 配置镜像加速器
注册阿里云账号后,即可在阿里云控制台看到类似如下的页面。
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. 参考的链接地址
送人玫瑰,手有余香!