共计 6556 个字符,预计需要花费 17 分钟才能阅读完成。
Boogie Software 是欧洲著名的金融科技公司,多年来致力于为银行提供 Fintech、AI、大数据高性能后端、移动应用程序、数据分析及 UX 等创新服务,帮助银行推动数字化转型。凭借过去十多年在该领域的独特经验,Boogie 已成为数字银行服务提供商中的领导者。
本文作者是 Boogie Software 的资深软件架构师 Jari Tenhunen。他拥有超过 15 年的软件开发经验,擅长信息安全和网络协议。并且长期管理项目和团队,在其中主导软件架构和技术,成功将多个产品推向市场。
Boogie Software 的 IT 团队在很多客户银行的核心银行业务数字化的项目中使用到了 Kubernetes 和容器技术,因此我们始终在想 Kubernetes 能如何使用更合适的硬件在本地工作。在本文中,我将详细介绍我们如何在树莓派上构建轻量级裸机集群,以在公司网络中运行应用程序和服务。
我们之所以这么做,有两个原因:第一,通过建立集群,我们将可以拥有一个平台,来可靠、灵活地运行公司网络内部应用程序和服务;第二,我们可以通过这次机会学习更多关于 Kubernetes、微服务以及容器的技能。如果你也想要参照我们的经验来构建一个相似的系统,我建议你至少要了解关于 Docker 容器、Kubernetes 关键概念(节点、pod、服务、deployment 等)以及 IP 网络的基础知识。
硬件准备
你需要准备以下设备:
- 树莓派 2B/3B/3B+ 的型号,至少一个。你甚至在单个开发板上运行某些应用程序,但是建议使用两个或更多的开发板来分散负载并增加冗余。
- 电源和可用于树莓派的 SD 卡,现有的以太网交换机或空闲端口以及一些电缆。
在我们的设置中,我们目前有 4 个树莓派 3 代 B + 开发板,所以在集群中有一个 master/server 和 3 个代理节点。如果树莓派有外壳当然更好,我们的同事用 3d 打印机设计了一个。此外,机壳的背面有两个用于冷却的风扇,每个开发板都位于一个托盘上,该托盘可以热插拔以进行维护。这些托盘前面还设有 activity/heartbeat LED 和电源开关的位置,它们都连接到开发板的 GPIO 接头。
软件准备
对于 Kubernetes 的实现,我们使用的是 k3s。k3s 是由 Rancher Labs 推出的一款轻量级、通过 CNCF 一致性认证的 Kubernetes 发行版。尽管这是一款刚推出不久的产品,但它真的十分稳定和易用,可以实现秒级启动。让 k3s 从其他轻量的 Kubernetes 发行版脱颖而出的原因是,k3s 可供生产使用,而诸如 microk8s 或 Minikube 之类的项目则无法实现这一目的,并且 k3s 十分轻巧,还可以在基于 ARM 的硬件上很好地运行。在 k3s 中,任何设备上安装 Kubernetes 所需的一切都包含在这一个 40MB 的二进制文件当中。
k3s 几乎能在任何 Linux 发行版中很好地运行,因此我们决定将 Raspbian Stretch Lite 作为基础 OS,因为我们不需要在开发板上添加任何额外的服务或者桌面 UI。k3s 确实需要在 Linux 内核中启用 cgroup,这可以在 Raspbian 上通过向 /boot/cmdline.txt: 添加以下参数来实现:
cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
安装 k3s
k3s 非常友好的地方在于,它可以实现平滑安装过程。你准备好你的 server 硬件之后,仅需几分钟就可以完成设置,因为它仅需一行命令就能安装 server(主节点):
curl -sfL https://get.k3s.io | sh -
代理节点也是如此:
curl -sfL https://get.k3s.io | K3S_TOKEN=<token_from_server> K3S_URL=https://<server_ip>:6443 sh -
其中 token_from_server 是来自服务器的文件 / var / lib / rancher / k3s / server / node-token 的内容,server_ip 是服务器节点的 IP 地址。至此,我们的集群已经启动并正在运行,我们可以开始部署工作负载:
root@k3s-server:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k3s-node1 Ready <none> 40s v1.13.4-k3s.1
k3s-server Ready <none> 108s v1.13.4-k3s.1
为了管理和监控集群,我们安装了 Kubernetes Dashboard,它能够提供给非常方便的 web 界面来查看整个系统的状态、执行管理员操作并访问日志。同时,本地安装和运行 kubectl 命令也非常有帮助,因为它可以让你从自己的计算机管理集群,而无需 ssh 进入集群。为此,你只需要安装 kubectl,然后将集群信息从服务器节点 config /etc/rancher/k3s/k3s.yaml 复制到本地 kubeconfig 文件中(通常是 ${HOME}/.kube/config)。
使用负载均衡器暴露服务
默认情况下,部署在 Kubernetes 集群上的应用程序仅可以在集群中获取(默认服务类型是 ClusterIP)。如果想要从集群外部获取应用程序,有两个选项。你可以使用 NodePort 类型配置服务,该服务在静态端口的每个节点 IP 上暴露服务,你也可以使用负载均衡器(服务类型 LoadBalancer)。然而,NodePort 服务有限制:它们使用自己专用的端口范围,我们只能通过端口号来区分应用。k3s 内置了一个简单的负载均衡器,但由于它使用的是节点的 IP 地址,我们可能很快就会用完 IP/ 端口组合并且无法将服务绑定到某个虚拟 IP。基于这些原因,我们决定部署 MetalLB——一种用于裸机集群的负载均衡器实现。
只需应用 YAML manifest 即可安装 MetalLB。在现有网络中运行 MetalLB 的最简单方法是使用所谓的第 2 层模式,这意味着集群节点通过 ARP 协议宣布本地网络中服务的虚拟 IP。为此,我们从内部网络保留了一小部分 IP 地址用于集群服务。MetalLB 的配置如下所示:
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: company-office
protocol: layer2
addresses:
- 10.10.10.50-10.10.10.99
使用此配置,集群服务将被暴露在范围为 10.10.10.50—10.10.10.99 的地址中。为了绑定服务到指定的 IP,你可以在服务清单中使用 loadBalancerIP 参数:
apiVersion: v1
kind: Service
metadata:
name: my-web-app
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
loadBalancerIP: 10.10.10.51
selector:
app: my-web-app
type: LoadBalancer
在负载均衡中,我们面临诸多挑战。例如,Kubernetes 中限制在单个负载均衡器中同时使用 TCP 和 UDP 端口。要解决这一问题,你可以定义两个服务实例,一个用于 TCP 端口,另一个用于 UDP 端口。其缺点是,除非启用 IP 地址共享,否则你需要在不同的 IP 地址中运行这两个服务。而且,由于 MetalLB 是一个年轻项目,因此也存在一些小问题,但我们相信这些很快都会得到解决。
添加存储
k3s 暂时没有内置的存储解决方案,所以为了使 Pod 能够访问持久性文件存储,我们需要使用 Kubernetes 的插件来创建一个。由于 Kubernetes 的目标之一是使应用程序与基础架构解耦并使其可移植,因此 Kubernetes 中用 PersistentVolume(PV)和 PersistentVolumeClaim(PVC)的概念定义了用于存储的抽象层。详细的概念解释可以参照我们之前发过的文章:详解 Kubernetes 存储关键概念。PV 是通常由管理员配置并可供应用程序使用的存储资源。另一方面,PVC 描述了应用程序对某种类型和一定数量的存储的需求。创建 PVC(通常作为应用程序的一部分)时,如果有一个尚未使用且满足应用程序 PVC 要求的可用 PVC,它将绑定到 PV。配置和维护所有这些需要手动工作,因此动态配置卷应运而生。
在我们的基础架构中,我们已经有一个现有的 NFS 服务器,因此我们决定将其用于集群持久性文件存储。在我们的案例中,最简单的方法是使用支持动态配置 PV 的 NFS-Client Provisioner。Provisioner 只需在现有的 NFS 共享上为每个新 PV(集群映射到 PVC)上创建新目录,然后将 PV 目录挂载在使用它的容器中。这样就无需配置 NFS 共享到单个 pod 中的卷,而是全部动态运行。
为 ARM 交叉构建容器镜像
显然,在基于 ARM 的硬件上(如树莓派)运行应用程序容器时,需要根据 ARM 的架构构建容器。在 ARM 架构容器中构建自己的应用程序时,可能会遇到一些陷阱。首先,基础镜像需要可用于你的目标架构体系。对于树莓派 3 来说,通常需要使用 arm32v7 的基础镜像,它们可以在大部分 Docker 镜像仓库中被调用。所以,当交叉构建应用程序时,确保你的 Dockerfile 包含以下代码:
FROM arm32v7/alpine:latest
第二件需要注意的事是,你的主机 Docker 需要能够运行 ARM 二进制文件。如果你在 mac 上运行 Docker,那操作将十分轻松,因为它对此有内置支持。如果是在 Linux 上,你需要执行一些步骤:
添加 QEMU 二进制文件到你的基础镜像
为了在 Linux 上的 Docker 中运行 ARM 二进制文件,镜像需要一个 QEMU 二进制文件。你可以选择一个已经包含了 QEMU 二进制文件的基础镜像,也可以在镜像构建过程中复制
qemu-arm-static 二进制文件到其中,例如,通过将以下行添加到你的 Dockerfile 中:
COPY --from=biarms/qemu-bin /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static
安全警示:请注意下载和运行未知的容器就如同下载和运行位置的.exe 文件。除业余项目外,其他任何项目都应使用扫描 / 审核过的镜像(如 Docker 官方镜像)或来自信任的组织和公司的容器镜像。
然后,你需要在创建 Docker 镜像的主机 OS 上注册 QEMU。这可以简单地通过以下方式实现:
docker run --rm --privileged multiarch/qemu-user-static:register --reset
可以在构建实际镜像之前将该命令添加到你的构建脚本中。总结一下,你的 Dockerfile.arm 应该看起来像这样:
FROM arm32v7/alpine:latest
COPY --from=biarms/qemu-bin /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static
# commands to build your app go here…
# e.g. RUN apk add --update <pkgs that you need…>
并且你的 build /CI 脚本应该是:
docker run --rm --privileged multiarch/qemu-user-static:register --reset
docker build -t my-custom-image-arm . -f Dockerfile.arm
这将为你提供 ARM 架构的容器镜像。如果你对细节很感兴趣,请参阅:
https://www.ecliptik.com/Cros…
自动化构建和上传到镜像仓库
最后一步是自动化整个流程,以便容器镜像可以自动构建并且自动上传到一个镜像仓库,在那里可以轻松地将其部署到我们地 k3s 集群。在内部,我们使用 GitLab 进行源代码管理和 CI/CD,因此我们自然希望在其中运行这些构建,它甚至包括一个内置的容器镜像仓库,因此不需要设置单独的镜像仓库。
关于构建 Docker 镜像,GitLab 有十分完善的文档(https://docs.gitlab.com/ee/ci…),因此我们不在此赘述。在为 docker 构建配置 GitLab Runner 之后,剩下要做的就是为该项目创建.gitlab-ci.yml 文件。在我们的例子中,它看起来像这样:
image: docker:stable
stages:
- build
- release
variables:
DOCKER_DRIVER: overlay2
CONTAINER_TEST_IMAGE: ${CI_REGISTRY_IMAGE}/${CI_PROJECT_NAME}-arm:${CI_COMMIT_REF_SLUG}
CONTAINER_RELEASE_IMAGE: ${CI_REGISTRY_IMAGE}/${CI_PROJECT_NAME}-arm:latest
before_script:
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
build_image:
stage: build
script:
- docker pull $CONTAINER_RELEASE_IMAGE || true
- docker run --rm --privileged multiarch/qemu-user-static:register --reset
- docker build --cache-from $CONTAINER_RELEASE_IMAGE -t $CONTAINER_TEST_IMAGE . -f Dockerfile.arm
- docker push $CONTAINER_TEST_IMAGE
release:
stage: release
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
- docker push $CONTAINER_RELEASE_IMAGE
既然在容器镜像仓库中我们有了我们的镜像,我们只需要将它们部署到我们的集群中。为了授予集群访问镜像仓库的权限,我们在 GitLab 中创建了一个 deploy 令牌,然后将令牌凭据作为 docker-registry 密钥添加到集群中:
kubectl create secret docker-registry deploycred --docker-server=<your-registry-server> --docker-username=<token-username> --docker-password=<token-password> --docker-email=<your-email>
之后,可以在 YAML 文件 PodSpec 中使用 deploy 令牌密钥:
imagePullSecrets:
- name: deploycred
containers:
- name: myapp
image: gitlab.mycompany.com:4567/my/project/my-app-arm:latest
完成所有这些步骤之后,我们终于拥有了一个从私有镜像仓库中的源代码到 ARM 容器镜像的自动 CI / CD 流水线,可以将其部署到集群中。
结 语
总而言之,事实证明,建立和运行自己的裸机 Kubernetes 集群比预期的要容易。而且 k3s 确实是在边缘计算场景中和一般配置较低的硬件上运行容器化服务的明智选择。
一个小缺点是 k3s 尚不支持高可用(多主设备配置)。尽管单个主服务器设置已经具有相当的弹性,因为即使主服务器离线,服务仍可在代理节点上继续运行,我们还是希望为主节点提供一些冗余。显然,此功能正在开发中,但在此功能可用之前,我们建议从服务器节点配置中进行备份。