分类 容器与云 下的文章

将 Kubernetes 安装在多个树莓派上,实现自己的“家庭私有云”容器服务。

Kubernetes 从一开始就被设计为云原生的企业级容器编排系统。它已经成长为事实上的云容器平台,并由于接受了容器原生虚拟化和无服务器计算等新技术而继续发展。

从微型的边缘计算到大规模的容器环境,无论是公有云还是私有云环境,Kubernetes 都可以管理其中的容器。它是“家庭私有云”项目的理想选择,既提供了强大的容器编排,又让你有机会了解一项这样的技术 —— 它的需求如此之大,与云计算结合得如此彻底,以至于它的名字几乎就是“云计算”的代名词。

没有什么比 Kubernetes 更懂“云”,也没有什么能比树莓派更合适“集群起来”!在廉价的树莓派硬件上运行本地的 Kubernetes 集群是获得在真正的云技术巨头上进行管理和开发的经验的好方法。

在树莓派上安装 Kubernetes 集群

本练习将在三个或更多运行 Ubuntu 20.04 的树莓派 4 上安装 Kubernetes 1.18.2 集群。Ubuntu 20.04(Focal Fossa)提供了针对 64 位 ARM(ARM64)的树莓派镜像(64 位内核和用户空间)。由于目标是使用这些树莓派来运行 Kubernetes 集群,因此运行 AArch64 容器镜像的能力非常重要:很难找到 32 位的通用软件镜像乃至于标准基础镜像。借助 Ubuntu 20.04 的 ARM64 镜像,可以让你在 Kubernetes 上使用 64 位容器镜像。

AArch64 vs. ARM64;32 位 vs. 64 位;ARM vs. x86

请注意,AArch64 和 ARM64 实际上是同一种东西。不同的名称源于它们在不同社区中的使用。许多容器镜像都标为 AArch64,并能在标为 ARM64 的系统上正常运行。采用 AArch64/ARM64 架构的系统也能够运行 32 位的 ARM 镜像,但反之则不然:32 位的 ARM 系统无法运行 64 位的容器镜像。这就是 Ubuntu 20.04 ARM64 镜像如此有用的原因。

这里不会太深入地解释不同的架构类型,值得注意的是,ARM64/AArch64 和 x86\_64 架构是不同的,运行在 64 位 ARM 架构上的 Kubernetes 节点无法运行为 x86\_64 构建的容器镜像。在实践中,你会发现有些镜像没有为两种架构构建,这些镜像可能无法在你的集群中使用。你还需要在基于 Arch64 的系统上构建自己的镜像,或者跳过一些限制以让你的常规的 x86\_64 系统构建 Arch64 镜像。在“家庭私有云”项目的后续文章中,我将介绍如何在常规系统上构建 AArch64 镜像。

为了达到两全其美的效果,在本教程中设置好 Kubernetes 集群后,你可以在以后向其中添加 x86\_64 节点。你可以通过使用 Kubernetes 的 污点 taint 容忍 toleration 能力,由 Kubernetes 的调度器将给定架构的镜像调度到相应的节点上运行。

关于架构和镜像的内容就不多说了。是时候安装 Kubernetes 了,开始吧!

前置需求

这个练习的要求很低。你将需要:

  • 三台(或更多)树莓派 4(最好是 4GB 内存的型号)。
  • 在全部树莓派上安装 Ubuntu 20.04 ARM64。

为了简化初始设置,请阅读《修改磁盘镜像来创建基于树莓派的家庭实验室》,在将 Ubuntu 镜像写入 SD 卡并安装在树莓派上之前,添加一个用户和 SSH 授权密钥(authorized_keys)。

配置主机

在 Ubuntu 被安装在树莓派上,并且可以通过 SSH 访问后,你需要在安装 Kubernetes 之前做一些修改。

安装和配置 Docker

截至目前,Ubuntu 20.04 在 base 软件库中提供了最新版本的 Docker,即 v19.03,可以直接使用 apt 命令安装它。请注意,包名是 docker.io。请在所有的树莓派上安装 Docker:

# 安装 docker.io 软件包
$ sudo apt install -y docker.io

安装好软件包后,你需要做一些修改来启用 cgroup(控制组)。cgroup 允许 Linux 内核限制和隔离资源。实际上,这可以让 Kubernetes 更好地管理其运行的容器所使用的资源,并通过让容器彼此隔离来增加安全性。

在对所有树莓派进行以下修改之前,请检查 docker info 的输出:

# 检查 `docker info`
# 省略了某些输出
$ sudo docker info
(...)
 Cgroup Driver: cgroups
(...)
WARNING: No memory limit support
WARNING: No swap limit support
WARNING: No kernel memory limit support
WARNING: No kernel memory TCP limit support
WARNING: No oom kill disable support

上面的输出突出显示了需要修改的部分:cgroup 驱动和限制支持。

首先,将 Docker 使用的默认 cgroup 驱动从 cgroups 改为 systemd,让 systemd 充当 cgroup 管理器,确保只有一个 cgroup 管理器在使用。这有助于系统的稳定性,这也是 Kubernetes 所推荐的。要做到这一点,请创建 /etc/docker/daemon.json 文件或将内容替换为:

# 创建或替换 /etc/docker/daemon.json 以启用 cgroup 的 systemd 驱动

$ sudo cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

启用 cgroup 限制支持

接下来,启用限制支持,如上面的 docker info 输出中的警告所示。你需要修改内核命令行以在引导时启用这些选项。对于树莓派 4,将以下内容添加到 /boot/firmware/cmdline.txt 文件中:

cgroup_enable=cpuset
cgroup_enable=memory
cgroup_memory=1
swapaccount=1

确保它们被添加到 cmdline.txt 文件的行末。这可以通过使用 sed 在一行中完成。

# 将 cgroup 和交换选项添加到内核命令行中
# 请注意 "cgroup_enable=cpuset" 前的空格,以便在该行的最后一个项目后添加一个空格
$ sudo sed -i '$ s/$/ cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 swapaccount=1/' /boot/firmware/cmdline.txt

sed 命令匹配该行的终止符(由第一个 $ 代表),用列出的选项代替它(它实际上是将选项附加到该行)。

有了这些改变,Docker 和内核应该按照 Kubernetes 的需要配置好了。重新启动树莓派,当它们重新启动后,再次检查 docker info 的输出。现在,Cgroups driver 变成了 systemd,警告也消失了。

允许 iptables 查看桥接流量

根据文档,Kubernetes 需要配置 iptables 来查看桥接网络流量。你可以通过修改 sysctl 配置来实现。

# 启用 net.bridge.bridge-nf-call-iptables 和 -iptables6
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
$ sudo sysctl --system

安装 Ubuntu 的 Kubernetes 包

由于你使用的是 Ubuntu,你可以从 Kubernetes.io 的 apt 仓库中安装 Kubernetes 软件包。目前没有 Ubuntu 20.04(Focal)的仓库,但最近的 Ubuntu LTS 仓库 Ubuntu 18.04(Xenial) 中有 Kubernetes 1.18.2。最新的 Kubernetes 软件包可以从那里安装。

将 Kubernetes 软件库添加到 Ubuntu 的源列表之中:

# 添加 packages.cloud.google.com 的 atp 密钥
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

# 添加 Kubernetes 软件库
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF

当 Kubernetes 添加了 Ubuntu 20.04(Focal)仓库时 —— 也许是在下一个 Kubernetes 版本发布时 —— 请确保切换到它。

将仓库添加到源列表后,安装三个必要的 Kubernetes 包:kubelet、kubeadm 和 kubectl:

# 更新 apt 缓存并安装 kubelet、kubeadm kubectl
# (输出略)
$ sudo apt update &amp;&amp; sudo apt install -y kubelet kubeadm kubectl

最后,使用 apt-mark hold 命令禁用这三个包的定期更新。升级到 Kubernetes 需要比一般的更新过程更多的手工操作,需要人工关注。

# 禁止(标记为保持)Kubernetes 软件包的更新
$ sudo apt-mark hold kubelet kubeadm kubectl
kubelet set on hold.
kubeadm set on hold.
kubectl set on hold.

主机配置就到这里了! 现在你可以继续设置 Kubernetes 本身了。

创建 Kubernetes 集群

在安装了 Kubernetes 软件包之后,你现在可以继续创建集群了。在开始之前,你需要做一些决定。首先,其中一个树莓派需要被指定为控制平面节点(即主节点)。其余的节点将被指定为计算节点。

你还需要选择一个 CIDR(无类别域间路由)地址用于 Kubernetes 集群中的 Pod。在集群创建过程中设置 pod-network-cidr 可以确保设置了 podCIDR 值,它以后可以被 容器网络接口 Container Network Interface (CNI)加载项使用。本练习使用的是 FlannelCNI。你选择的 CIDR 不应该与你的家庭网络中当前使用的任何 CIDR 重叠,也不应该与你的路由器或 DHCP 服务器管理的 CIDR 重叠。确保使用一个比你预期需要的更大的子网:总是有比你最初计划的更多的 Pod!在这个例子中,我将使用 CIDR 地址 10.244.0.0/16,但你可以选择一个适合你的。

有了这些决定,你就可以初始化控制平面节点了。用 SSH 或其他方式登录到你为控制平面指定的节点。

初始化控制平面

Kubernetes 使用一个引导令牌来验证被加入集群的节点。当初始化控制平面节点时,需要将此令牌传递给 kubeadm init 命令。用 kubeadm token generate 命令生成一个令牌:

# 生成一个引导令牌来验证加入集群的节点
$ TOKEN=$(sudo kubeadm token generate)
$ echo $TOKEN
d584xg.xupvwv7wllcpmwjy

现在你可以使用 kubeadm init 命令来初始化控制平面了:

# 初始化控制平面
#(输出略)
$ sudo kubeadm init --token=${TOKEN} --kubernetes-version=v1.18.2 --pod-network-cidr=10.244.0.0/16

如果一切顺利,你应该在输出的最后看到类似这样的东西:

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.2.114:6443 --token zqqoy7.9oi8dpkfmqkop2p5 \
    --discovery-token-ca-cert-hash sha256:71270ea137214422221319c1bdb9ba6d4b76abfa2506753703ed654a90c4982b

注意两点:第一,Kubernetes 的 kubectl 连接信息已经写入到 /etc/kubernetes/admin.conf。这个 kubeconfig 文件可以复制到用户的 ~/.kube/config 中,可以是主节点上的 root 用户或普通用户,也可以是远程机器。这样你就可以用 kubectl 命令来控制你的集群。

其次,输出中以 kubernetes join 开头的最后一行是你可以运行的命令,你可以运行这些命令加入更多的节点到集群中。

将新的 kubeconfig 复制到你的用户可以使用的地方后,你可以用 kubectl get nodes 命令来验证控制平面是否已经安装:

# 显示 Kubernetes 集群中的节点
# 你的节点名称会有所不同
$ kubectl get nodes
NAME         STATUS   ROLES    AGE     VERSION
elderberry   Ready    master   7m32s   v1.18.2

安装 CNI 加载项

CNI 加载项负责 Pod 网络的配置和清理。如前所述,这个练习使用的是 Flannel CNI 加载项,在已经设置好 podCIDR 值的情况下,你只需下载 Flannel YAML 并使用 kubectl apply 将其安装到集群中。这可以用 kubectl apply -f - 从标准输入中获取数据,用一行命令完成。这将创建管理 Pod 网络所需的 ClusterRoles、ServiceAccounts 和 DaemonSets 等。

下载并应用 Flannel YAML 数据到集群中:

# 下载 Flannel YAML 数据并应用它
# (输出略)
$ curl -sSL https://raw.githubusercontent.com/coreos/flannel/v0.12.0/Documentation/kube-flannel.yml | kubectl apply -f -

将计算节点加入到集群中

有了 CNI 加载项,现在是时候将计算节点添加到集群中了。加入计算节点只需运行 kube init 命令末尾提供的 kubeadm join 命令来初始化控制平面节点。对于你想加入集群的其他树莓派,登录主机,运行命令即可:

# 加入节点到集群,你的令牌和 ca-cert-hash 应各有不同
$ sudo kubeadm join 192.168.2.114:6443 --token zqqoy7.9oi8dpkfmqkop2p5 \
    --discovery-token-ca-cert-hash sha256:71270ea137214422221319c1bdb9ba6d4b76abfa2506753703ed654a90c4982b

一旦你完成了每个节点的加入,你应该能够在 kubectl get nodes 的输出中看到新节点:

# 显示 Kubernetes 集群中的节点
# 你的节点名称会有所不同
$ kubectl get nodes
NAME         STATUS   ROLES    AGE     VERSION
elderberry   Ready    master   7m32s   v1.18.2
gooseberry    Ready    &lt;none&gt;   2m39s   v1.18.2
huckleberry   Ready    &lt;none&gt;   17s     v1.18.2

验证集群

此时,你已经拥有了一个完全正常工作的 Kubernetes 集群。你可以运行 Pod、创建部署和作业等。你可以使用服务从集群中的任何一个节点访问集群中运行的应用程序。你可以通过 NodePort 服务或入口控制器实现外部访问。

要验证集群正在运行,请创建一个新的命名空间、部署和服务,并检查在部署中运行的 Pod 是否按预期响应。此部署使用 quay.io/clcollins/kube-verify:01 镜像,这是一个监听请求的 Nginx 容器(实际上,与文章《使用 Cloud-init 将节点添加到你的私有云》中使用的镜像相同)。你可以在这里查看镜像的容器文件。

为部署创建一个名为 kube-verify 的命名空间:

# 创建一个新的命名空间
$ kubectl create namespace kube-verify
# 列出命名空间
$ kubectl get namespaces
NAME              STATUS   AGE
default           Active   63m
kube-node-lease   Active   63m
kube-public       Active   63m
kube-system       Active   63m
kube-verify       Active   19s

现在,在新的命名空间创建一个部署:

# 创建一个新的部署
$ cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kube-verify
  namespace: kube-verify
  labels:
    app: kube-verify
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kube-verify
  template:
    metadata:
      labels:
        app: kube-verify
    spec:
      containers:
      - name: nginx
        image: quay.io/clcollins/kube-verify:01
        ports:
        - containerPort: 8080
EOF
deployment.apps/kube-verify created

Kubernetes 现在将开始创建部署,它由三个 Pod 组成,每个 Pod 都运行 quay.io/clcollins/kube-verify:01 镜像。一分钟左右后,新的 Pod 应该运行了,你可以用 kubectl get all -n kube-verify 来查看它们,以列出新命名空间中创建的所有资源:

# 检查由该部署创建的资源
$ kubectl get all -n kube-verify
NAME                               READY   STATUS              RESTARTS   AGE
pod/kube-verify-5f976b5474-25p5r   0/1     Running             0          46s
pod/kube-verify-5f976b5474-sc7zd   1/1     Running             0          46s
pod/kube-verify-5f976b5474-tvl7w   1/1     Running             0          46s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kube-verify   3/3     3            3           47s

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/kube-verify-5f976b5474   3         3         3       47s

你可以看到新的部署、由部署创建的复制子集,以及由复制子集创建的三个 Pod,以满足部署中的 replicas: 3 的要求。你可以看到 Kubernetes 内部工作正常。

现在,创建一个服务来暴露在三个 Pod 中运行的 Nginx “应用程序”(在本例中是“欢迎”页面)。这将作为一个单一端点,你可以通过它连接到 Pod:

# 为该部署创建服务
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Service
metadata:
  name: kube-verify
  namespace: kube-verify
spec:
  selector:
    app: kube-verify
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
EOF
service/kube-verify created

创建服务后,你可以对其进行检查并获取新服务的 IP 地址:

# 检查新服务
$ kubectl get -n kube-verify service/kube-verify
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kube-verify   ClusterIP   10.98.188.200   &lt;none&gt;        80/TCP    30s

你可以看到 kube-verify 服务已经被分配了一个 ClusterIP(仅对集群内部)10.98.188.200。这个 IP 可以从你的任何节点到达,但不能从集群外部到达。你可以通过在这个 IP 上连接到部署内部的容器来验证它们是否在工作:

# 使用 curl 连接到 ClusterIP:
# (简洁期间,输出有删节)
$ curl 10.98.188.200
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>

成功了!你的服务正在运行,容器内的 Nginx 正在响应你的请求。你的服务正在运行,容器内的 Nginx 正在响应你的请求。

此时,你的树莓派上有一个正在运行的 Kubernetes 集群,安装了一个 CNI 加载项(Flannel),并有一个运行 Nginx Web 服务器的测试部署和服务。在大型公有云中,Kubernetes 有不同的入口控制器来与不同的解决方案交互,比如最近报道的 Skipper 项目。同样,私有云也有与硬件负载均衡器设备(如 F5 Networks 的负载均衡器)交互的入口控制器,或用于处理进入节点的流量的 Nginx 和 HAProxy 控制器。

在以后的文章中,我将通过安装自己的入口控制器来解决将集群中的服务暴露给外界的问题。我还将研究动态存储供应器和 StorageClasses,以便为应用程序分配持久性存储,包括利用你在上一篇文章《将树莓派家庭实验室变成网络文件系统》中设置的 NFS 服务器来为你的 Pod 创建按需存储。

去吧,Kubernetes

“Kubernetes”(κυβερνήτης)在希腊语中是飞行员的意思 —— 但这是否意味着驾驶船只以及引导船只的人?诶,不是。“Kubernan”(κυβερνάω)是希腊语“驾驶”或“引导”的意思,因此,去吧,Kubernan,如果你在会议上或其它什么活动上看到我,请试着给我一个动词或名词的通行证,以另一种语言 —— 我不会说的语言。

免责声明:如前所述,我不会读也不会讲希腊语,尤其是古希腊语,所以我选择相信我在网上读到的东西。你知道那是怎么一回事。我对此有所保留,放过我吧,因为我没有开“对我来说都是希腊语”这种玩笑。然而,只是提一下,虽然我可以开玩笑,但是实际上没有,所以我要么偷偷摸摸,要么聪明,要么两者兼而有之。或者,两者都不是。我并没有说这是个好笑话。

所以,去吧,像专业人员一样在你的家庭私有云中用自己的 Kubernetes 容器服务来试运行你的容器吧!当你越来越得心应手时,你可以修改你的 Kubernetes 集群,尝试不同的选项,比如前面提到的入口控制器和用于持久卷的动态 StorageClasses。

这种持续学习是 DevOps 的核心,持续集成和新服务交付反映了敏捷方法论,当我们学会了处理云实现的大规模扩容,并发现我们的传统做法无法跟上步伐时,我们就接受了这两种方法论。

你看,技术、策略、哲学、一小段希腊语和一个可怕的原始笑话,都汇聚在一篇文章当中。


via: https://opensource.com/article/20/6/kubernetes-raspberry-pi

作者:Chris Collins 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

像主流云提供商的处理方式一样,在家中添加机器到你的私有云。

Cloud-init 是一种广泛使用的行业标准方法,用于初始化云实例。云提供商使用 Cloud-init 来定制实例的网络配置、实例信息,甚至用户提供的配置指令。它也是一个可以在你的“家庭私有云”中使用的很好的工具,可以为你的家庭实验室的虚拟机和物理机的初始设置和配置添加一点自动化 —— 并了解更多关于大型云提供商是如何工作的信息。关于更多的细节和背景,请看我之前的文章《在你的树莓派家庭实验室中使用 Cloud-init》。

 title=

运行 Cloud-init 的 Linux 服务器的启动过程(Chris Collins,CC BY-SA 4.0

诚然,Cloud-init 对于为许多不同客户配置机器的云提供商来说,比对于由单个系统管理员运行的家庭实验室更有用,而且 Cloud-init 解决的许多问题对于家庭实验室来说可能有点多余。然而,设置它并了解它的工作原理是了解更多关于这种云技术的好方法,更不用说它是首次启动时配置设备的好方法。

本教程使用 Cloud-init 的 NoCloud 数据源,它允许 Cloud-init 在传统的云提供商环境之外使用。本文将向你展示如何在客户端设备上安装 Cloud-init,并设置一个运行 Web 服务的容器来响应客户端的请求。你还将学习如何审查客户端从 Web 服务中请求的内容,并修改 Web 服务的容器,以提供基本的、静态的 Cloud-init 服务。

在现有系统上设置 Cloud-init

Cloud-init 可能在新系统首次启动时最有用,它可以查询配置数据,并根据指令对系统进行定制。它可以包含在树莓派和单板计算机的磁盘镜像中,也可以添加到用于 配给 provision 虚拟机的镜像中。对于测试用途来说,无论是在现有系统上安装并运行 Cloud-init,还是安装一个新系统,然后设置 Cloud-init,都是很容易的。

作为大多数云提供商使用的主要服务,大多数 Linux 发行版都支持 Cloud-init。在这个例子中,我将使用 Fedora 31 Server 来安装树莓派,但在 Raspbian、Ubuntu、CentOS 和大多数其他发行版上也可以用同样的方式来完成。

安装并启用云计算初始服务

在你想作为 Cloud-init 客户端的系统上,安装 Cloud-init 包。如果你使用的是 Fedora:

# Install the cloud-init package
dnf install -y cloud-init

Cloud-init 实际上是四个不同的服务(至少在 systemd 下是这样),这些服务负责检索配置数据,并在启动过程的不同阶段进行配置更改,这使得可以做的事情更加灵活。虽然你不太可能直接与这些服务进行太多交互,但在你需要排除一些故障时,知道它们是什么还是很有用的。它们是:

  • cloud-init-local.service
  • cloud-init.service
  • cloud-config.service
  • cloud-final.service

启用所有四个服务:

# Enable the four cloud-init services
systemctl enable cloud-init-local.service
systemctl enable cloud-init.service
systemctl enable cloud-config.service
systemctl enable cloud-final.service

配置数据源以查询

启用服务后,请配置数据源,客户端将从该数据源查询配置数据。有许多数据源类型,而且大多数都是为特定的云提供商配置的。对于你的家庭实验室,请使用 NoCloud 数据源,(如上所述)它是为在没有云提供商的情况下使用 Cloud-init 而设计的。

NoCloud 允许以多种方式包含配置信息:以内核参数中的键/值对,用于在启动时挂载的 CD(或虚拟机中的虚拟 CD);包含在文件系统中的文件中;或者像本例中一样,通过 HTTP 从指定的 URL(“NoCloud Net” 选项)获取配置信息。

数据源配置可以通过内核参数提供,也可以在 Cloud-init 配置文件 /etc/cloud/cloud.cfg 中进行设置。该配置文件对于使用自定义磁盘镜像设置 Cloud-init 或在现有主机上进行测试非常方便。

Cloud-init 还会合并在 /etc/cloud/cloud.cfg.d/ 中找到的任何 *.cfg 文件中的配置数据,因此为了保持整洁,请在 /etc/cloud/cloud.cfg.d/10_datasource.cfg 中配置数据源。Cloud-init 可以通过使用以下语法从 seedfrom 键指向的 HTTP 数据源中读取数据。

seedfrom: http://ip_address:port/

IP 地址和端口是你将在本文后面创建的 Web 服务。我使用了我的笔记本电脑的 IP 和 8080 端口。这也可以是 DNS 名称。

创建 /etc/cloud/cloud.cfg.d/10_datasource.cfg 文件:

# Add the datasource:
# /etc/cloud/cloud.cfg.d/10_datasource.cfg

# NOTE THE TRAILING SLASH HERE!
datasource:
  NoCloud:
    seedfrom: http://ip_address:port/

客户端设置就是这样。现在,重新启动客户端后,它将尝试从你在 seedfrom 键中输入的 URL 检索配置数据,并进行必要的任何配置更改。

下一步是设置一个 Web 服务器来侦听客户端请求,以便你确定需要提供的服务。

设置网络服务器以审查客户请求

你可以使用 Podman 或其他容器编排工具(如 Docker 或 Kubernetes)快速创建和运行 Web 服务器。这个例子使用的是 Podman,但同样的命令也适用于 Docker。

要开始,请使用 fedora:31 容器镜像并创建一个容器文件(对于 Docker 来说,这会是一个 Dockerfile)来安装和配置 Nginx。从该容器文件中,你可以构建一个自定义镜像,并在你希望提供 Cloud-init 服务的主机上运行它。

创建一个包含以下内容的容器文件:

FROM fedora:31

ENV NGINX_CONF_DIR "/etc/nginx/default.d"
ENV NGINX_LOG_DIR "/var/log/nginx"
ENV NGINX_CONF "/etc/nginx/nginx.conf"
ENV WWW_DIR "/usr/share/nginx/html"

# Install Nginx and clear the yum cache
RUN dnf install -y nginx \
      && dnf clean all \
      && rm -rf /var/cache/yum

# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout ${NGINX_LOG_DIR}/access.log \
    && ln -sf /dev/stderr ${NGINX_LOG_DIR}/error.log

# Listen on port 8080, so root privileges are not required for podman
RUN sed -i -E 's/(listen)([[:space:]]*)(\[\:\:\]\:)?80;$/\1\2\38080 default_server;/' $NGINX_CONF
EXPOSE 8080

# Allow Nginx PID to be managed by non-root user
RUN sed -i '/user nginx;/d' $NGINX_CONF
RUN sed -i 's/pid \/run\/nginx.pid;/pid \/tmp\/nginx.pid;/' $NGINX_CONF

# Run as an unprivileged user
USER 1001

CMD ["nginx", "-g", "daemon off;"]

注:本例中使用的容器文件和其他文件可以在本项目的 GitHub 仓库中找到。

上面容器文件中最重要的部分是改变日志存储方式的部分(写到 STDOUT 而不是文件),这样你就可以在容器日志中看到进入该服务器的请求。其他的一些改变使你可以在没有 root 权限的情况下使用 Podman 运行容器,也可以在没有 root 权限的情况下运行容器中的进程。

在 Web 服务器上的第一个测试并不提供任何 Cloud-init 数据;只是用它来查看 Cloud-init 客户端的请求。

创建容器文件后,使用 Podman 构建并运行 Web 服务器镜像:

# Build the container image
$ podman build -f Containerfile -t cloud-init:01 .

# Create a container from the new image, and run it
# It will listen on port 8080
$ podman run --rm -p 8080:8080 -it cloud-init:01

这会运行一个容器,让你的终端连接到一个伪 TTY。一开始看起来什么都没有发生,但是对主机 8080 端口的请求会被路由到容器内的 Nginx 服务器,并且在终端窗口中会出现一条日志信息。这一点可以用主机上的 curl 命令进行测试。

# Use curl to send an HTTP request to the Nginx container
$ curl http://localhost:8080

运行该 curl 命令后,你应该会在终端窗口中看到类似这样的日志信息:

127.0.0.1 - - [09/May/2020:19:25:10 +0000] "GET / HTTP/1.1" 200 5564 "-" "curl/7.66.0" "-"

现在,有趣的部分来了:重启 Cloud-init 客户端,并观察 Nginx 日志,看看当客户端启动时, Cloud-init 向 Web 服务器发出了什么请求。

当客户端完成其启动过程时,你应该会看到类似的日志消息。

2020/05/09 22:44:28 [error] 2#0: *4 open() "/usr/share/nginx/html/meta-data" failed (2: No such file or directory), client: 127.0.0.1, server: _, request: "GET /meta-data HTTP/1.1", host: "instance-data:8080"
127.0.0.1 - - [09/May/2020:22:44:28 +0000] "GET /meta-data HTTP/1.1" 404 3650 "-" "Cloud-Init/17.1" "-"

注:使用 Ctrl+C 停止正在运行的容器。

你可以看到请求是针对 /meta-data 路径的,即 http://ip_address_of_the_webserver:8080/meta-data。这只是一个 GET 请求 —— Cloud-init 并没有向 Web 服务器发送任何数据。它只是盲目地从数据源 URL 中请求文件,所以要由数据源来识别主机的要求。这个简单的例子只是向任何客户端发送通用数据,但一个更大的家庭实验室应该需要更复杂的服务。

在这里,Cloud-init 请求的是实例元数据信息。这个文件可以包含很多关于实例本身的信息,例如实例 ID、分配实例的主机名、云 ID,甚至网络信息。

创建一个包含实例 ID 和主机名的基本元数据文件,并尝试将其提供给 Cloud-init 客户端。

首先,创建一个可复制到容器镜像中的 meta-data 文件。

instance-id: iid-local01
local-hostname: raspberry
hostname: raspberry

实例 ID(instance-id)可以是任何东西。但是,如果你在 Cloud-init 运行后更改实例 ID,并且文件被送达客户端,就会触发 Cloud-init 再次运行。你可以使用这种机制来更新实例配置,但你应该意识到它是这种工作方式。

local-hostnamehostname 键正如其名,它们会在 Cloud-init 运行时为客户端设置主机名信息。

在容器文件中添加以下行以将 meta-data 文件复制到新镜像中。

# Copy the meta-data file into the image for Nginx to serve it
COPY meta-data ${WWW_DIR}/meta-data

现在,用元数据文件重建镜像(使用一个新的标签以方便故障排除),并用 Podman 创建并运行一个新的容器。

# Build a new image named cloud-init:02
podman build -f Containerfile -t cloud-init:02 .

# Run a new container with this new meta-data file
podman run --rm -p 8080:8080 -it cloud-init:02

新容器运行后,重启 Cloud-init 客户端,再次观察 Nginx 日志。

127.0.0.1 - - [09/May/2020:22:54:32 +0000] "GET /meta-data HTTP/1.1" 200 63 "-" "Cloud-Init/17.1" "-"
2020/05/09 22:54:32 [error] 2#0: *2 open() "/usr/share/nginx/html/user-data" failed (2: No such file or directory), client: 127.0.0.1, server: _, request: "GET /user-data HTTP/1.1", host: "instance-data:8080"
127.0.0.1 - - [09/May/2020:22:54:32 +0000] "GET /user-data HTTP/1.1" 404 3650 "-" "Cloud-Init/17.1" "-"

你看,这次 /meta-data 路径被提供给了客户端。成功了!

然而,客户端接着在 /user-data 路径上寻找第二个文件。该文件包含实例所有者提供的配置数据,而不是来自云提供商的数据。对于一个家庭实验室来说,这两个都是你自己提供的。

你可以使用许多 user-data 模块来配置你的实例。对于这个例子,只需使用 write_files 模块在客户端创建一些测试文件,并验证 Cloud-init 是否工作。

创建一个包含以下内容的用户数据文件:

#cloud-config

# Create two files with example content using the write_files module
write_files:
 - content: |
    "Does cloud-init work?"
   owner: root:root
   permissions: '0644'
   path: /srv/foo
 - content: |
   "IT SURE DOES!"
   owner: root:root
   permissions: '0644'
   path: /srv/bar

除了使用 Cloud-init 提供的 user-data 模块制作 YAML 文件外,你还可以将其制作成一个可执行脚本供 Cloud-init 运行。

创建 user-data 文件后,在容器文件中添加以下行,以便在重建映像时将其复制到镜像中:

# Copy the user-data file into the container image
COPY user-data ${WWW_DIR}/user-data

重建镜像,并创建和运行一个新的容器,这次使用用户数据信息:

# Build a new image named cloud-init:03
podman build -f Containerfile -t cloud-init:03 .

# Run a new container with this new user-data file
podman run --rm -p 8080:8080 -it cloud-init:03

现在,重启 Cloud-init 客户端,观察 Web 服务器上的 Nginx 日志:

127.0.0.1 - - [09/May/2020:23:01:51 +0000] "GET /meta-data HTTP/1.1" 200 63 "-" "Cloud-Init/17.1" "-"
127.0.0.1 - - [09/May/2020:23:01:51 +0000] "GET /user-data HTTP/1.1" 200 298 "-" "Cloud-Init/17.1" "-

成功了!这一次,元数据和用户数据文件都被送到了 Cloud-init 客户端。

验证 Cloud-init 已运行

从上面的日志中,你知道 Cloud-init 在客户端主机上运行并请求元数据和用户数据文件,但它用它们做了什么?你可以在 write_files 部分验证 Cloud-init 是否写入了你在用户数据文件中添加的文件。

在 Cloud-init 客户端上,检查 /srv/foo/srv/bar 文件的内容:

# cd /srv/ && ls
bar foo
# cat foo
"Does cloud-init work?"
# cat bar
"IT SURE DOES!"

成功了!文件已经写好了,并且有你期望的内容。

如上所述,还有很多其他模块可以用来配置主机。例如,用户数据文件可以配置成用 apt 添加包、复制 SSH 的 authorized_keys、创建用户和组、配置和运行配置管理工具等等。我在家里的私有云中使用它来复制我的 authorized_keys、创建一个本地用户和组,并设置 sudo 权限。

你接下来可以做什么

Cloud-init 在家庭实验室中很有用,尤其是专注于云技术的实验室。本文所演示的简单服务对于家庭实验室来说可能并不是超级有用,但现在你已经知道 Cloud-init 是如何工作的了,你可以继续创建一个动态服务,可以用自定义数据配置每台主机,让家里的私有云更类似于主流云提供商提供的服务。

在数据源稍显复杂的情况下,将新的物理(或虚拟)机器添加到家中的私有云中,可以像插入它们并打开它们一样简单。


via: https://opensource.com/article/20/5/create-simple-cloud-init-service-your-homelab

作者:Chris Collins 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

了解了云行业的标准,该向你的家庭实验室自动添加新设备和用户了。

Cloud-init(可以说)是一个标准,云提供商用它来为云实例提供初始化和配置数据。它最常用于新实例的首次启动,以自动完成网络设置、账户创建和 SSH 密钥安装等使新系统上线所需的任何事情,以便用户可以访问它。

在之前的一篇文章《修改磁盘镜像来创建基于树莓派的家庭实验室》中,我展示了如何为像树莓派这样的单板计算机定制操作系统镜像以实现类似的目标。有了 Cloud-init,就不需要向镜像中添加自定义数据。一旦在镜像中启用了它,你的虚拟机、物理服务器,甚至是小小的树莓派都可以表现得像你自己的 “家庭私有云” 中的云计算实例。新机器只需插入、打开,就可以自动成为你的家庭实验室的一部分。

说实话,Cloud-init 的设计并没有考虑到家庭实验室。正如我所提到的,你可以很容易地修改给定的一套系统磁盘镜像,以启用 SSH 访问并在第一次启动后对它们进行配置。Cloud-init 是为大规模的云提供商设计的,这些提供商需要容纳许多客户,维护一组小的镜像,并为这些客户提供访问实例的机制,而无需为每个客户定制一个镜像。拥有单个管理员的家庭实验室则不会面临同样的挑战。

不过,Cloud-init 在家庭实验室中也不是没有可取之处。教育是我的家庭私有云项目的目标之一,而为你的家庭实验室设置 Cloud-init 是一个很好的方式,可以获得大大小小的云提供商大量使用的技术的经验。Cloud-init 也是其他初始配置选项的替代方案之一。与其为家庭实验室中的每台设备定制每个镜像、ISO 等,并在你要进行更改时面临繁琐的更新,不如直接启用 Cloud-init。这减少了技术债务 —— 还有什么比个人技术债务更糟糕的吗?最后,在你的家庭实验室中使用 Cloud-init 可以让你的私有云实例与你拥有的或将来可能拥有的任何公有云实例表现相同 —— 这是真正的混合云

关于 Cloud-init

当为 Cloud-init 配置的实例启动并且服务开始运行时(实际上是 systemd 中的四个服务,以处理启动过程中的依赖关系),它会检查其配置中的数据源,以确定其运行在什么类型的云中。每个主要的云提供商都有一个数据源配置,告诉实例在哪里以及如何检索配置信息。然后,实例使用数据源信息检索云提供商提供的配置信息(如网络信息和实例识别信息)和客户提供的配置数据(如要复制的授权密钥、要创建的用户账户以及许多其他可能的任务)。

检索数据后,Cloud-init 再对实例进行配置:设置网络、复制授权密钥等,最后完成启动过程。然后,远程用户就可以访问它,准备好使用 AnsiblePuppet 等工具进行进一步的配置,或者准备好接收工作负载并开始分配任务。

配置数据

如上所述,Cloud-init 使用的配置数据来自两个潜在来源:云提供商和实例用户。在家庭实验室中,你扮演着这两种角色:作为云提供商提供网络和实例信息,作为用户提供配置信息。

云提供商元数据文件

在你的云提供商角色中,你的家庭实验室数据源将为你的私有云实例提供一个元数据文件。这个元数据文件包含实例 ID、云类型、Python 版本(Cloud-init 用 Python 编写并使用 Python)或要分配给主机的 SSH 公钥等信息。如果你不使用 DHCP(或 Cloud-init 支持的其他机制,如镜像中的配置文件或内核参数),元数据文件还可能包含网络信息。

用户提供的用户数据文件

Cloud-init 的真正价值在于用户数据文件。用户数据文件由用户提供给云提供商,并包含在数据源中,它将实例从一台普通的机器变成了用户舰队的一员。用户数据文件可以以可执行脚本的形式出现,与正常情况下脚本的工作方式相同;也可以以云服务配置 YAML 文件的形式出现,利用 Cloud-init 的模块 来执行配置任务。

数据源

数据源是由云提供商提供的服务,它为实例提供了元数据和用户数据文件。实例镜像或 ISO 被配置为告知实例正在使用什么数据源。

例如,亚马逊 AWS 提供了一个 link-local 文件,它将用实例的自定义数据来响应实例的 HTTP 请求。其他云提供商也有自己的机制。幸运的是,对于家庭私有云项目来说,也有 NoCloud 数据源。

NoCloud 数据源允许通过内核命令以键值对的形式提供配置信息,或通过挂载的 ISO 文件系统以用户数据和元数据文件的形式提供。这些对于虚拟机来说很有用,尤其是与自动化搭配来创建虚拟机。

还有一个 NoCloudNet 数据源,它的行为类似于 AWS EC2 数据源,提供一个 IP 地址或 DNS 名称,通过 HTTP 从这里检索用户数据和元数据。这对于你的家庭实验室中的物理机器来说是最有帮助的,比如树莓派、NUC 或多余的服务器设备。虽然 NoCloud 可以工作,但它需要更多的人工关注 —— 这是云实例的反模式。

家庭实验室的 Cloud-init

我希望这能让你了解到 Cloud-init 是什么,以及它对你的家庭实验室有何帮助。它是一个令人难以置信的工具,被主要的云提供商所接受,在家里使用它可以是为了教育和乐趣,并帮助你自动向实验室添加新的物理或虚拟服务器。之后的文章将详细介绍如何创建简单的静态和更复杂的动态 Cloud-init 服务,并指导你将它们纳入你的家庭私有云。


via: https://opensource.com/article/20/5/cloud-init-raspberry-pi-homelab

作者:Chris Collins 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

值此周年纪念之际,来通过这些深度文章和实践项目了解下 Kubernetes。

在云原生的成长期,开发者们发现在一个小型的、原子化的、精简的 Linux 镜像里编写应用程序很方便,这些镜像与它们所运行的服务器共享资源。从技术上讲,这些基于内核命名空间的小环境定义被称为容器。随着容器的激增,系统管理员们很快意识到,开发一个不仅能帮助他们管理容器,还能帮助他们管理下面的虚拟化基础设施的工具变得至关重要。于是,Kubernetes 应运而生。

Kubernetes 是一个可扩展开源平台,用于管理容器。它可以帮助管理员和开发者们围绕容器管理工作负载、服务和进程。它促进了声明式配置,更容易实现自动化。在它相对较短的生命周期中,它已经催生了一个迅速成长的生态系统,其中包括来自大量公司和项目的服务、支持和工具。

如果你想对这项重要的云技术有更多的了解,这里有一些能帮忙你更深入学习的文章。还有 5 个项目可以帮你把学到的东西付诸实践。

遏制容器乱象

2016 年,我们发布了《使用 Kubernetes 遏制容器乱象》,这是一篇由 Terry Ryan 写的关于 Kubernetes 的介绍性文章,讲述了 Kubernetes 如何帮助管理员和架构师们努力应对容器。如果你想找一篇从底层介绍容器是做什么的以及 Kubernetes 是如何实现容器管理的,那么你应该先读下本文。本文适合零基础的读者,解释了所有重要的概念,因此你能迅速了解相关技术。

如果想进阶了解内核层面发生的一些神奇的事情,请阅读 Jessica Cherry 对 Kubernetes 命名空间的解释。

延伸阅读:

Kubernetes:为什么它很重要?

Kubernetes 提供了 基础设施即服务 Infrastructure-as-a-Service (IaaS)解决方案(类似 OpenStack)的便利和一个完整的 平台即服务 Platform as a Service (PaaS)。它为你提供了管理基础设施的抽象能力,以及在裸金属基础层面进行故障排除所需的工具。如果你执着于使用单一的裸金属服务器,你可能需要阅读下 Tim Potter 写的《你为什么需要 Kubernetes》。他的文章对比了 IaaS 和 PaaS,解释了为什么 Kubernetes 如此广泛地被使用。你可能并不是一定需要 Kubernetes 或容器,但是重要的是知道什么情况下需要。

延伸阅读:

在树莓派上运行 Kubernetes

熟悉 Kubernetes 的最好方法莫过于自己运行它。不幸的是,不是每个人都有一个云服务基层设施(或者有足够的钱来租用一个)可供其支配。而幸运的是,Chris Collins 提供了《在树莓派上运行 Kubernetes》的教程。结合他的另外几篇关于《Cloud-init》和《Cloud-init 服务》的教程(也是在树莓派上运行),你可以搭建任何你想要的家庭实验室,这样你就可以学习如何管理属于自己的开放混合云。

Kubernetes 命令

一旦你运行起 Kubernetes 后,可以看看 Jessica Cherry 的文章和附带的备忘清单,这个清单列出了所有的基本的 Kubernetes 命令。在她的文章中,她解释了 kubectl 命令的语法,简单讲述了每个命令和子命令是用来做什么的。

有趣的 Kubernetes 项目

没有什么比拥有技术却不知道该怎么用它更令人沮丧的了。例如,在你的办公桌上有一个树莓派是一回事,但是决定它的 CPU 应该用来做什么工作却完全是另一回事。我们发布了很多教程,来指导你完成你的 Kubernetes 之路的探索:

最重要的,花点时间来熟悉容器和 Kubernetes。不论你先把容器化的应用放到服务器、云上还是桌面,它们都是能帮助你理解的重要的范例,因为它们是一个强大的构造,可以让 Linux 的应用变得更好、更健壮、鲁棒性更好、更简单。一定要投入精力去学习它们,你不会后悔的。


via: https://opensource.com/article/20/6/kubernetes-anniversary

作者:Seth Kenlon 选题:lujun9972 译者:lxbwolf 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在编辑“容器如何工作”爱好者杂志的能力页面时,我想试着解释一下为什么 strace 在 Docker 容器中无法工作。

这里的问题是 —— 如果我在笔记本上的 Docker 容器中运行 strace,就会出现这种情况:

$ docker run  -it ubuntu:18.04 /bin/bash
$ # ... install strace ...
[email protected]:/# strace ls
strace: ptrace(PTRACE_TRACEME, ...): Operation not permitted

strace 通过 ptrace 系统调用起作用,所以如果不允许使用 ptrace,它肯定是不能工作的! 这个问题很容易解决 —— 在我的机器上,是这样解决的:

docker run --cap-add=SYS_PTRACE  -it ubuntu:18.04 /bin/bash

但我对如何修复它不感兴趣,我想知道为什么会出现这种情况。为什么 strace 不能工作,为什么--cap-add=SYS_PTRACE 可以解决这个问题?

假设 1:容器进程缺少 CAP_SYS_PTRACE 能力。

我一直以为原因是 Docker 容器进程默认不具备 CAP_SYS_PTRACE 能力。这和它可以被 --cap-add=SYS_PTRACE 修复是一回事,是吧?

但这实际上是不合理的,原因有两个。

原因 1:在实验中,作为一个普通用户,我可以对我的用户运行的任何进程进行 strace。但如果我检查我的当前进程是否有 CAP_SYS_PTRACE 能力,则没有:

$ getpcaps $$
Capabilities for `11589': =

原因 2:capabilities 的手册页对 CAP_SYS_PTRACE 的介绍是:

CAP_SYS_PTRACE
       * Trace arbitrary processes using ptrace(2);

所以,CAP_SYS_PTRACE 的作用是让你像 root 一样,可以对任何用户拥有的任意进程进行 ptrace。你不需要用它来对一个只是由你的用户拥有的普通进程进行 ptrace

我用第三种方法测试了一下(LCTT 译注:此处可能原文有误) —— 我用 docker run --cap-add=SYS_PTRACE -it ubuntu:18.04 /bin/bash 运行了一个 Docker 容器,去掉了 CAP_SYS_PTRACE 能力,但我仍然可以跟踪进程,虽然我已经没有这个能力了。什么?为什么?!

假设 2:关于用户命名空间的事情?

我的下一个(没有那么充分的依据的)假设是“嗯,也许这个过程是在不同的用户命名空间里,而 strace 不能工作,因为某种原因而行不通?”这个问题其实并不相关,但这是我观察时想到的。

容器进程是否在不同的用户命名空间中?嗯,在容器中:

root@e27f594da870:/# ls /proc/$$/ns/user -l
... /proc/1/ns/user -> 'user:[4026531837]'

在宿主机:

bork@kiwi:~$ ls /proc/$$/ns/user -l
... /proc/12177/ns/user -> 'user:[4026531837]'

因为用户命名空间 ID(4026531837)是相同的,所以容器中的 root 用户和主机上的 root 用户是完全相同的用户。所以,绝对没有理由不能够对它创建的进程进行 strace!

这个假设并没有什么意义,但我(之前)没有意识到 Docker 容器中的 root 用户和主机上的 root 用户同一个,所以我觉得这很有意思。

假设 3:ptrace 系统的调用被 seccomp-bpf 规则阻止了

我也知道 Docker 使用 seccomp-bpf 来阻止容器进程运行许多系统调用。而 ptrace被 Docker 默认的 seccomp 配置文件阻止的系统调用列表中!(实际上,允许的系统调用列表是一个白名单,所以只是ptrace 不在默认的白名单中。但得出的结果是一样的。)

这很容易解释为什么 strace 在 Docker 容器中不能工作 —— 如果 ptrace 系统调用完全被屏蔽了,那么你当然不能调用它,strace 就会失败。

让我们来验证一下这个假设 —— 如果我们禁用了所有的 seccomp 规则,strace 能在 Docker 容器中工作吗?

$ docker run --security-opt seccomp=unconfined -it ubuntu:18.04  /bin/bash
$ strace ls
execve("/bin/ls", ["ls"], 0x7ffc69a65580 /* 8 vars */) = 0
... it works fine ...

是的,很好用!很好。谜底解开了,除了…..

为什么 --cap-add=SYS_PTRACE 能解决问题?

我们还没有解释的是:为什么 --cap-add=SYS_PTRACE 可以解决这个问题?

docker run 的手册页是这样解释 --cap-add 参数的。

--cap-add=[]
   Add Linux capabilities

这跟 seccomp 规则没有任何关系! 怎么回事?

我们来看看 Docker 源码

当文档没有帮助的时候,唯一要做的就是去看源码。

Go 语言的好处是,因为依赖关系通常是在一个 Go 仓库里,你可以通过 grep 来找出做某件事的代码在哪里。所以我克隆了 github.com/moby/moby,然后对一些东西进行 grep,比如 rg CAP_SYS_PTRACE

我认为是这样的。在 containerd 的 seccomp 实现中,在 contrib/seccomp/seccomp/seccomp\_default.go 中,有一堆代码来确保如果一个进程有一个能力,那么它也会(通过 seccomp 规则)获得访问权限,以使用与该能力相关的系统调用。

case "CAP_SYS_PTRACE":
       s.Syscalls = append(s.Syscalls, specs.LinuxSyscall{
           Names: []string{
               "kcmp",
               "process_vm_readv",
               "process_vm_writev",
               "ptrace",
           },
           Action: specs.ActAllow,
           Args:   []specs.LinuxSeccompArg{},
       })

在 moby 中的 profile/seccomp/seccomp.go默认的 seccomp 配置文件中,也有一些其他的代码似乎做了一些非常类似的事情,所以有可能就是这个代码在做这个事情。

所以我想我们有答案了!

Docker 中的 --cap-add 做的事情比它说的要多

结果似乎是,--cap-add 并不像手册页里说的那样,它更像是 --cap-add-and-also-whiteelist-some-extra-system-calls-if-required。这很有意义! 如果你具有一个像 --CAP_SYS_PTRACE 这样的能力,可以让你使用 process_vm_readv 系统调用,但是该系统调用被 seccomp 配置文件阻止了,那对你没有什么帮助!

所以当你给容器 CAP_SYS_PTRACE 能力时,允许使用 process_vm_readvptrace 系统调用似乎是一个合理的选择。

就这样!

这是个有趣的小事情,我认为这是一个很好的例子,说明了容器是由许多移动的部件组成的,它们以不完全显而易见的方式一起工作。


via: https://jvns.ca/blog/2020/04/29/why-strace-doesnt-work-in-docker/

作者:Julia Evans 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

看看这个很酷的 Kubernetes 管理的终端 UI。

通常情况下,我写的关于 Kubernetes 管理的文章中用的都是做集群管理的 kubectl 命令。然而最近,有人给我介绍了 k9s 项目,可以让我快速查看并解决 Kubernetes 中的日常问题。这极大地改善了我的工作流程,我会在这篇教程中告诉你如何上手它。

它可以安装在 Mac、Windows 和 Linux 中,每种操作系统的说明可以在这里找到。请先完成安装,以便能够跟上本教程。

我会使用 Linux 和 Minikube,这是一种在个人电脑上运行 Kubernetes 的轻量级方式。按照此教程或使用该文档来安装它。

设置 k9s 配置文件

安装好 k9s 应用后,从帮助命令开始总是很好的起点。

$ k9s help

正如你在帮助信息所看到的,我们可以用 k9s 来配置很多功能。我们唯一需要进行的步骤就是编写配置文件。而 info 命令会告诉我们该应用程序要在哪里找它的配置文件。

$ k9s info
 ____  __.________
|    |/ _/   __   \______
|      < \____    /  ___/
|    |  \   /    /\___ \
|____|__ \ /____//____  >
        \/            \/

Configuration:   /Users/jess/.k9s/config.yml
Logs:            /var/folders/5l/c1y1gcw97szdywgf9rk1100m0000gn/T/k9s-jess.log
Screen Dumps:    /var/folders/5l/c1y1gcw97szdywgf9rk1100m0000gn/T/k9s-screens-jess

如果要添加配置文件,该配置目录不存在的话就创建它,然后添加一个配置文件。

$ mkdir -p ~/.k9s/
$ touch ~/.k9s/config.yml

在这篇介绍中,我们将使用 k9s 版本库中推荐的默认 config.yml。维护者请注意,这种格式可能会有变化,可以在这里查看最新版本。

k9s:
  refreshRate: 2
  headless: false
  readOnly: false
  noIcons: false
  logger:
    tail: 200
    buffer: 500
    sinceSeconds: 300
    fullScreenLogs: false
    textWrap: false
    showTime: false
  currentContext: minikube
  currentCluster: minikube
  clusters:
    minikube:
      namespace:
        active: ""
        favorites:
        - all
        - kube-system
        - default
      view:
        active: dp
  thresholds:
    cpu:
      critical: 90
      warn: 70
    memory:
      critical: 90
      warn: 70

我们设置了 k9s 寻找本地的 minikube 配置,所以我要确认 minikube 已经上线可以使用了。

$ minikube status
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

运行 k9s 来探索一个 Kubernetes 集群

有了配置文件,并指向我们的本地集群,我们现在可以运行 k9s 命令了。

$ k9s

启动后,会弹出 k9s 的基于文本的用户界面。在没有指定命名空间标志的情况下,它会向你显示默认命名空间中的 Pod。

 title=

如果你运行在一个有很多 Pod 的环境中,默认视图可能会让人不知所措。或者,我们可以将注意力集中在给定的命名空间上。退出该应用程序,运行 k9s -n <namespace>,其中 <namespace> 是已存在的命名空间。在下图中,我运行了 k9s -n minecraft,它显示了我损坏的 Pod:

 title=

所以,一旦你有了 k9s 后,有很多事情你可以更快地完成。

通过快捷键来导航 k9s,我们可以随时使用方向键和回车键来选择列出的项目。还有不少其他的通用快捷键可以导航到不同的视图。

  • 0:显示在所有命名空间中的所有 Pod  title=
  • d:描述所选的 Pod  title=
  • l:显示所选的 Pod 的日志  title=

你可能会注意到 k9s 设置为使用 Vim 命令键,包括使用 JK 键上下移动等。Emacs 用户们,败退吧 :)

快速查看不同的 Kubernetes 资源

需要去找一个不在 Pod 里的东西吗?是的,我也需要。当我们输入冒号(:)键时,可以使用很多快捷方式。从那里,你可以使用下面的命令来导航。

  • :svc:跳转到服务视图  title=
  • :deploy:跳转到部署视图  title=
  • :rb:跳转到角色绑定视图,用于 基于角色的访问控制(RBAC)管理  title=
  • :namespace:跳转到命名空间视图  title=
  • :cj:跳转到 cronjob 视图,查看集群中计划了哪些作业。  title=

这个应用最常用的工具是键盘;要在任何页面往上或下翻页,请使用方向键。如果你需要退出,记得使用 Vim 绑定键,键入 :q,然后按回车键离开。

用 k9s 排除 Kubernetes 的故障示例

当出现故障的时候,k9s 怎么帮忙?举个例子,我让几个 Pod 由于配置错误而死亡。下面你可以看到我那个可怜的 “hello” 部署死了。当我们将其高亮显示出来,可以按 d 运行 describe 命令,看看是什么原因导致了故障。

 title=

 title=

草草掠过那些事件并不能告诉我们故障原因。接下来,我按了 esc 键,然后通过高亮显示 Pod 并输入shift-l 来检查日志。

 title=

不幸的是,日志也没有提供任何有用的信息(可能是因为部署从未正确配置过),而且 Pod 也没有出现。

然后我使用 esc 退了出来,我看看删除 Pod 是否能解决这个问题。要做到这一点,我高亮显示该 Pod,然后使用 ctrl-d。幸好 k9s 在删除前会提示用户。

 title=

虽然我确实删除了这个 Pod,但部署资源仍然存在,所以新的 Pod 会重新出现。无论什么原因(我们还不知道),它还会继续重启并死掉。

在这里,我会重复查看日志,描述资源,甚至使用 e 快捷方式来编辑运行中的 Pod 以排除故障行为。在这个特殊情况下,失败的 Pod 是因为没有配置在这个环境下运行。因此,让我们删除部署来停止崩溃接着重启的循环。

我们可以通过键入 :deploy 并点击回车进入部署。从那里我们高亮显示并按 ctrl-d 来删除。

 title=

 title=

这个有问题的部署被干掉了! 只用了几个按键就把这个失败的部署给清理掉了。

k9s 是极其可定制的

这个应用有很多自定义选项、乃至于 UI 的配色方案。这里有几个可编辑的选项,你可能会感兴趣。

  • 调整你放置 config.yml 文件的位置(这样你就可以把它存储在版本控制中)。
  • alias.yml 文件中添加自定义别名
  • hotkey.yml 文件中创建自定义热键
  • 探索现有的插件或编写自己的插件。

整个应用是在 YAML 文件中配置的,所以定制化对于任何 Kubernetes 管理员来说都会觉得很熟悉。

用 k9s 简化你的生活

我倾向于以一种非常手动的方式来管理我团队的系统,更多的是为了锻炼脑力,而不是别的。当我第一次听说 k9s 的时候,我想,“这只是懒惰的 Kubernetes 而已。”于是我否定了它,然后回到了到处进行人工干预的状态。实际上,当我在处理积压工作时就开始每天使用它,而让我震惊的是它比单独使用 kubectl 快得多。现在,我已经皈依了。

了解你的工具并掌握做事情的“硬道理”很重要。还有一点很重要的是要记住,就管理而言,重要的是要更聪明地工作,而不是更努力。使用 k9s,就是我践行这个目标的方法。我想,我们可以把它叫做懒惰的 Kubernetes 管理,也没关系。


via: https://opensource.com/article/20/5/kubernetes-administration

作者:Jessica Cherry 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出