容器中的容器——利用Dind实现开箱即用的K3s

2024-02-10 10:20

本文主要是介绍容器中的容器——利用Dind实现开箱即用的K3s,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我在学习 Rancher 和 Minikube 的时候,发现它们都可以在自己的容器环境中提供一个 K3s 或 K8s 集群。尤其是 Minikube ,用户可以在它的容器环境中执行 docker ps 等命令,这种套娃一般的 docker in docker 体验有点儿意思。经过自己的调研和上手实践,我成功利用 Dind 结合轻量化的 K3s 实现了开箱即用的 dind-k3s 。

用途场景

在研究这件事情的时候,我就在想:容器的最佳使用实践,一直是推崇在一个容器中尽量只启动一个进程。这么做的好处是避免了多个进程之间相互影响,通过一个进程的好坏,就可以判定容器运行的状态。这一最佳实践在微服务领域体现的很好,一个容器运行一个微服务进程,进程挂掉,容器也会立即有运行失败的反馈,配合 K8s 等调度编排系统的存活探针(Liveness Probe)与失败重启策略,可以实现微服务快速恢复。

那么研究容器中运行容器这种看似更为复杂的使用方法,甚至还要在其中运行 K8s 等更复杂的系统,是否真的只是为了炫技呢?我在认真思考过后,认为起码在以下场景中,这种使用方法是很有价值的。

  • 开发测试环境:当开发者的项目依托于 K8s 体系进行开发时,这种使用方法很有价值。对于一个普通开发者而言,如何快速部署一套 K8s 环境还是比较考验技术的。毕竟术业有专攻,部署环境这种事情,还是专业的运维人员做起来更加顺手一些。但是使用 dind-k3s 就会好很多,通过一条 docker run 命令,就可以快速启动一个运行在容器中的 K3s 环境。所有的开发测试工作都在容器中进行,保障了环境的高度一致性。
  • CI/CD流水线:在容器中运行一系列的任务,并在最终生成构建产物并打包为镜像之后,将镜像推送到镜像仓库中保存起来。这个过程对于在容器中可以使用 docker build 等命令的能力是一种刚需。
  • 边缘设备场景:当我们需要在一个边缘设备上,部署比较复杂的终端业务系统时,交付的技术成本是很高的。工程师需要逐台设备一一配置,低效得很。如果在目前已实现的 dind-k3s 的基础上稍加改造,在 K3s 中自动化的初始一些资源,那么就很方便的完成了业务系统的部署。这种部署方式还会使业务系统享受到 K3s 提供的自动化运维能力,降低部署成本的同时,保障了业务的存活能力。

Dind 简述

Dind (docker in docker)并不是一件新鲜的事务,docker 官方很早就支持这么做。而我是这几天才了解到这一技术,这让工作于云原生领域,天天和容器打交道的我非常汗颜🤦‍♂️。这一技术的实现,要满足两个要点:

  • 在镜像中安装 docker,这里的 docker 不是指日常使用的客户端二进制命令,更重要的是安装 dockerd。
  • 容器需要以特权模式启动,特权模式为容器提供访问宿主机环境的所有权限,所以要注意这个行为的风险性。

除了上述的必要条件之外,还需要注重 Dind 的数据持久化。Dind 容器的持久化目录是需要被保存的,和在VM上直接启动的 docker 服务一样,这个目录就是 docker 的 data-root 目录,默认是 /var/lib/docker

在有些环境中,这个目录必须被挂载到宿主机上去,比如我在 Intel 芯片联想老爷机上启动的 ubuntu1804 虚拟机,就遭遇了 overlay2 driver not supported ;然而在 MacOS 中启动时,却不可以随便挂载到宿主机上去,会遭遇权限问题,我依然不知道为什么会出现这个问题,欢迎解决这个问题的小伙伴在评论里指出。

K3s 简述

K3s 是 Rancher 推出的轻量化 k8s 环境。安装部署非常简单,而且使用起来和 k8s 没有区别。我最终选择使用它的原因就是看中了它的轻量化特征。毕竟我的最终目标不是单纯的在容器中搭建 k8s 环境,而是部署自己的业务进去。

K3s 默认使用的容器运行时同时支持 containerd 与 docker。我由于后续要部署的业务对 docker 有较强的依赖,故而需要将 K3s 和 Dind 结合起来。K3s 同时支持部署 traefik 作为 Ingress Controller ,可以非常方便的利用 Ingress Rules 暴露服务。

最终形态

Dind-K3s.drawio

具体实现

我已经将相关代码上传至代码仓库中,供感兴趣的人查看:

  • dind-k3s on Gitee
  • dind-k3s on GitHub

代码的结构如下:

.
├── Dockerfile              # 构建 dind-k3s          
├── README.en.md
├── README.md
├── docker-entrypoint.sh    # 容器启动脚本
└── utils├── daemon.json         # dockerd 配置文件├── dind.conf           # dockerd k3s 的进程管理配置文件├── k3s-conf.yaml       # k3s 配置文件└── supervisord.conf    # supervisord 配置文件

项目中的关键文件是 Dockerfile,全文如下:

FROM ubuntu:20.10
LABEL auther="guox@goodrain.com"
WORKDIR /app# 安装必要的依赖
RUN sed -i -e 's/ports.ubuntu.com/mirrors.aliyun.com/g' \-e 's/archive.ubuntu.com/mirrors.aliyun.com/g' \-e 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list \&& apt update \&& apt install -y supervisor iptables wget vim \&& rm -rf /var/lib/apt/lists/*
# 安装 docker k3s kubectl
# 根据构建环境的 CPU 架构区分下载地址
RUN Arch="$(arch)"; \case "$Arch" in \'x86_64') \docker_url='https://download.docker.com/linux/static/stable/x86_64/docker-20.10.11.tgz'; \k3s_url="https://github.com/rancher/k3s/releases/download/v1.22.3+k3s1/k3s" \kubectl_url="https://storage.googleapis.com/kubernetes-release/release/v1.22.3/bin/linux/amd64/kubectl" \;; \'aarch64') \docker_url='https://download.docker.com/linux/static/stable/aarch64/docker-20.10.11.tgz'; \k3s_url="https://github.com/rancher/k3s/releases/download/v1.22.3+k3s1/k3s-arm64" \kubectl_url="https://storage.googleapis.com/kubernetes-release/release/v1.22.3/bin/linux/arm64/kubectl" \;; \esac \&& wget -O docker.tgz "$docker_url" \&& tar xzf docker.tgz --strip-components 1 --directory /usr/local/bin/ \&& rm docker.tgz \&& mkdir -p /etc/docker \&& wget -O /usr/local/bin/k3s "$k3s_url" \&& wget -O /usr/local/bin/kubectl "$kubectl_url" 
# 文件的变更是最频繁的变更,把拷贝文件的过程放在安装软件包、下载大体积资源的后面,可以更合理的利用镜像构建缓存,极大的节约构建时间
ADD . .
# 配置文件的处理
RUN chmod +x /usr/local/bin/k3s /usr/local/bin/kubectl /app/docker-entrypoint.sh \&& mkdir -p /app/logs/ /app/k3s \&& cp utils/dind.conf /etc/supervisor/conf.d/dind.conf \&& cp utils/daemon.json /etc/docker/ \&& cp utils/k3s-conf.yaml /app/k3s/config.yaml \&& cp utils/supervisord.conf /etc/supervisor/supervisord.confVOLUME [ "/var/lib/docker", "/app/k3s" ]
# 启动脚本
ENTRYPOINT [ "/app/docker-entrypoint.sh" ]
CMD ["/usr/bin/supervisord"]
安装相关资源

最开始,我们需要在 dind-k3s 镜像中安装 docker 、k3s、kubectl 以及相关的软件包。这一操作被定义在了 Dockerfile。在安装过程中,可以根据执行构建操作的宿主机的 CPU 架构,选择对应的包进行安装。目前支持 x86_64 以及 arm64 两种架构。除此之外,dockerd 想要正常运行,还需要安装 iptables 软件包。同时安装的 supervisor 用于容器内部进程管理,放在后文中详细说明。所使用的版本列表如下:

  • Docker :20.10.11
  • K3s:v1.22.3+k3s1
  • Kubectl:v1.22.3
配置文件的处理

下一个重要步骤,是将 utils 下的配置文件,和启动脚本拷贝到镜像中,并且将配置文件分发到正确的位置中去。

实用 Tip:

文件的变更是最频繁的变更,把拷贝文件的过程放在安装软件包、下载大体积资源的后面,可以更合理的利用镜像构建缓存,极大的节约构建时间。

构建缓存的机制是,每执行一条指令,将会形成一个镜像层作为构建缓存。下一次构建时,有变化的指令之前的构建操作会引用上次构建已经形成的构建缓存。所以,应该尽量将耗时时间长的安装包、下载包的操作,放在靠前的 RUN 指令中执行。

启动脚本与CMD命令

最后,指定启动脚本和 CMD 命令。我在启动脚本中添加了一些试验性的逻辑操作,如变更 dockerd 启动参数之类的,大家可以自行参考。

在这里,我想重点解释的是启动脚本和 CMD 之间的关系。在 ENTRYPOINT 和 CMD 同时出现时,CMD 会作为参数传递给 ENTRYPOINT。当前的配置等效于 /app/docker-entrypoint.sh /usr/bin/supervisord 。所以,在 /app/docker-entrypoint.sh 脚本的最后,添加了 exec $@ 来承接后续的参数。

ENTRYPOINT [ "/app/docker-entrypoint.sh" ]
CMD ["/usr/bin/supervisord"]等效于bash -c /app/docker-entrypoint.sh /usr/bin/supervisord
Supervisor 进程管理工具

鉴于 dind-k3s 容器中,注定有至少两个进程 (docker、 k3s)要启动,那么就有必要引入一个进程管理工具来托管它们。在容器中使用 systemd 管理进程需要做很多特殊的操作,我转而选择了 supervisor 进行进程管理。

项目中的 utils/supervisord.conf 是 supervisord 本身所使用的配置文件,主要负责配置其日志路径,以及 被托管的进程配置文件的位置 。我将其定义为 /etc/supervisor/conf.d/*.conf ,符合要求的配置文件,其内部定义的进程都会受到 supervisord 的监管。换句话说,我需要将 docker 、 k3s 的启动方式定义到配置文件,放到上面说的路径中,容器启动时,supervisord 会负责带起它们的进程。

定义好的配置如下:

[program:dind]
priority=20
command=/usr/local/bin/dockerd
user=root
autostart=true
autorestart=true
restartpause=10
stdout_logfile=/app/logs/dind.log
stdout_logfile_maxbytes=10mb
stdout_logfile_backups=3
redirect_stderr=true[program:k3s]
depends_on=dind
priority=20
command=/usr/local/bin/k3s server --config /app/k3s/config.yaml
user=root
autostart=true
autorestart=true
restartpause=10
stdout_logfile=/app/logs/k3s.log
stdout_logfile_maxbytes=10mb
stdout_logfile_backups=3
redirect_stderr=true

dockerd 的配置文件为 /etc/docker/daemon.json,自定义的参数追加,参考 /app/docker-entrypoint.sh

k3s 的配置文件为 /app/k3s/config.yaml,该文件位于持久化目录中,修改其配置,只需要在宿主机直接修改该文件后重启 dind-k3s 容器。

supervisord 提供很多高级功能,在这里被用到的包括:

  • 依赖关系,这个是最重要的,k3s 开始启动需要在 dockerd 启动完成之后。

  • 自动重启策略

  • 日志输出路径、分割、尺寸限定

构建镜像

一切准备就绪,可以开始构建镜像了,构建命令需要在项目根目录下执行。

docker build -t dind-k3s .

经过检测,镜像可以在 x86_64 以及 arm64 环境下构建成功并使用。

启动容器

镜像构建完成后,需要以特权模式启动容器实例。

sudo docker run -d \
--name=my-dind-k3s \
--privileged \
-v ~/data/docker:/var/lib/docker \
-v ~/data/k3s:/app/k3s \
dind-k3s

如果需要容器内运行的容器,可以向外部暴露服务,则需要在启动命令中使用 -p 参数指定端口映射关系。比如使用 Traefik 时,需要映射 80、 443 两个端口;而使用 NodePort 时,则应映射 30000 + 的端口。

效果验证

进入容器,可以执行 docker 相关的命令,也可以使用 kubectl 和 k3s 进行交互。

image-20211205142621154

完结,撒花🎉


Rainbond是一个开源的云原生应用管理平台,使用简单,不需要懂容器和Kubernetes,支持管理多个Kubernetes集群,提供企业级应用的全生命周期管理,功能包括应用开发环境、应用市场、微服务架构、应用持续交付、应用运维、应用级多云管理等。

图片

这篇关于容器中的容器——利用Dind实现开箱即用的K3s的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/696843

相关文章

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

idea中创建新类时自动添加注释的实现

《idea中创建新类时自动添加注释的实现》在每次使用idea创建一个新类时,过了一段时间发现看不懂这个类是用来干嘛的,为了解决这个问题,我们可以设置在创建一个新类时自动添加注释,帮助我们理解这个类的用... 目录前言:详细操作:步骤一:点击上方的 文件(File),点击&nbmyHIgsp;设置(Setti

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

MySQL大表数据的分区与分库分表的实现

《MySQL大表数据的分区与分库分表的实现》数据库的分区和分库分表是两种常用的技术方案,本文主要介绍了MySQL大表数据的分区与分库分表的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录1. mysql大表数据的分区1.1 什么是分区?1.2 分区的类型1.3 分区的优点1.4 分

一文详解如何从零构建Spring Boot Starter并实现整合

《一文详解如何从零构建SpringBootStarter并实现整合》SpringBoot是一个开源的Java基础框架,用于创建独立、生产级的基于Spring框架的应用程序,:本文主要介绍如何从... 目录一、Spring Boot Starter的核心价值二、Starter项目创建全流程2.1 项目初始化(

Mysql删除几亿条数据表中的部分数据的方法实现

《Mysql删除几亿条数据表中的部分数据的方法实现》在MySQL中删除一个大表中的数据时,需要特别注意操作的性能和对系统的影响,本文主要介绍了Mysql删除几亿条数据表中的部分数据的方法实现,具有一定... 目录1、需求2、方案1. 使用 DELETE 语句分批删除2. 使用 INPLACE ALTER T

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE

mysql数据库重置表主键id的实现

《mysql数据库重置表主键id的实现》在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,本文主要介绍了mysql数据库重置表主键id的实现,具有一定的参考价值,感兴趣的可以了... 目录关键语法演示案例在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,当我们