容器中的容器——利用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

相关文章

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM