Karmada:混合多云下的应用管理

Karmada:混合多云下的应用管理

背景

过去几年,公有云凭借着更高扩展性、灵活性、可靠性和安全性,吸引了大量的企业将应用程序部署到公有云上。随着业务规模的不断扩张,企业出于某些原因,如避免厂商锁定、追求更低的延迟、更高的可靠性等,选择将应用部署在更多的公有云上;也有些企业出于数据敏感性等原因,选择将部分应用部署有私有环境中。后者也更像是将上云的过程拉长。不管是多云还是混合云,基础设施都不可避免的存在着差异,企业不得不在适配底层设施上投入了大量的人力物力。

Kubernetes 的出现,完美地解决了这一问题。除了屏蔽基础设施层的差异解决了跨平台的问题,还提供自动化的容器编排、更高的扩展性、弹性和高可用性,其背后更是有着庞大的社区的支持。Kubernetes 的风靡,得到了大量企业的青睐。随着时间的推移,企业使用多个 Kubernetes 集群管理应用的情况越来越普遍。

如何在跨越多个集群、甚至是混合多云的环境下来管理应用成了新的难题。Karmada 的出现正是要解决这一问题。

Karmada

Karmada 是 CNCF(Cloud Native Computing Foundation)下面的一个开源项目,旨在为 Kubernetes 集群提供一个平台来简化跨多个 Kubernetes 集群的应用程序部署和管理,并提高可用性和可扩展性。

借用一下官网的架构图。从图中可以看到 Karmada 提供了一个集中式控制平面,负责资源和策略的管理,以及资源的调度;数据平面则是其管理的集群,真正运行资源的集群。

控制平面的组件与 Kubernetes 的组件类似,也都是负责资源的调度。不同的是,Kubernetes 的控制平面负责将资源调度到计算节点,而 Karmada 的控制平面是将资源调度到某个集群。

拿 Deployment 的部署来说,在 Kubernetes 集群中,控制平面根据当前节点的资源情况选择某个或某些节点来运行 pod。在 Karmada 多集群下,创建了 Deployment 资源后,Karmada 控制平面根据策略将其调度到目标的集群:在目标集群中创建 Deployment 资源,所有集群中的副本数之和,就是期望的副本数。

下面通过示例来进行说明。

环境搭建

前置条件

  • Docker
  • k3d
  • kubectl
  • karmadactl
  • 1 台虚拟机(最低配置不低于 4c8g)

我们选择使用 k3d 在容器中创建 4 个 k3s 集群(控制平面集群 control-plane 和成员 cluster-1cluster-2cluster-3),这些集群通过本机 IP 地址和独立的端口进行通信。

创建多个集群

创建集群前通过下面的命令获取本地 IP,保存在变量中。

HOST_IP=$(if [ "$(uname)" == "Darwin" ]; then ipconfig getifaddr en0; else ip -o route get to 8.8.8.8 | sed -n 's/.*src \([0-9.]\+\).*/\1/p'; fi)

使用 k3d 的命令创建集群,集群 apiserver 的端口分别为 6444644564466447.

API_PORT=6444 #6444 6445 6446 6447
for CLUSTER_NAME in control-plane cluster-1 cluster-2 cluster-3
do
  k3d cluster create ${CLUSTER_NAME} \
    --image docker.io/rancher/k3s:v1.23.8-k3s2 \
    --api-port "${HOST_IP}:${API_PORT}" \
    --servers-memory 2g \
    --k3s-arg "--disable=traefik@server:0" \
    --network multi-clusters \
    --timeout 120s \
    --wait
    ((API_PORT=API_PORT+1))
done

创建完集群,为了便于集群的访问,设置如下变量来访问对应的集群。

k3d kubeconfig get control-plane > /tmp/cp.kubeconfig
k3d kubeconfig get cluster-1 > /tmp/c1.kubeconfig
k3d kubeconfig get cluster-2 > /tmp/c2.kubeconfig
k3d kubeconfig get cluster-3 > /tmp/c3.kubeconfig
#使用
k0="kubectl --kubeconfig /tmp/cp.kubeconfig"
k1="kubectl --kubeconfig /tmp/c1.kubeconfig"
k2="kubectl --kubeconfig /tmp/c2.kubeconfig"
k3="kubectl --kubeconfig /tmp/c3.kubeconfig"

安装 Karmada

在控制平面集群上安装 Karmada 进行初始化。

sudo karmadactl --kubeconfig /tmp/cp.kubeconfig init

完成初始化后,同样设置变量 kmd 来方便访问 Karmada 的控制平面,指定使用 Karmada apiserver 的配置文件(该配置文件是在初始化时自动生成的,所以上面的命令用)。

kmd="kubectl --kubeconfig /etc/karmada/karmada-apiserver.config"

加入集群

接下来将其他三个集群加入到联邦中(k3d 自动在集群名前加上了前缀 k3d-)。

karmadactl --kubeconfig /etc/karmada/karmada-apiserver.config  join k3d-cluster-1 --cluster-kubeconfig=/tmp/c1.kubeconfig
karmadactl --kubeconfig /etc/karmada/karmada-apiserver.config  join k3d-cluster-2 --cluster-kubeconfig=/tmp/c2.kubeconfig
karmadactl --kubeconfig /etc/karmada/karmada-apiserver.config  join k3d-cluster-3 --cluster-kubeconfig=/tmp/c3.kubeconfig

返回下面的信息说明集群加入成功。

cluster(k3d-cluster-1) is joined successfully
cluster(k3d-cluster-2) is joined successfully
cluster(k3d-cluster-3) is joined successfully

访问 Karmada 的控制平面查看集群列表。

$kmd get cluster -o wide
NAME            VERSION        MODE   READY   AGE   APIENDPOINT
k3d-cluster-1   v1.23.8+k3s2   Push   True    24m   https://10.0.0.8:6445
k3d-cluster-2   v1.23.8+k3s2   Push   True    24m   https://10.0.0.8:6446
k3d-cluster-3   v1.23.8+k3s2   Push   True    23m   https://10.0.0.8:6447

至此多集群环境已经搭建完成。

测试

这里选择两种应用:服务端应用响应 HTTP 请求,返回当前 pod 的名字;客户端应用,可以用来发起 HTTP 请求。

部署服务端应用

通过 Karmada 的控制平面创建 2 个副本的 Deployment 和 Service。

$kmd apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: pipy
  name: pipy
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pipy
  strategy: {}
  template:
    metadata:
      labels:
        app: pipy
    spec:
      containers:
      - image: flomesh/pipy
        name: pipy
        command: ["pipy"]
        args: ["-e", "pipy().listen(8080).serveHTTP(() => new Message(os.env['HOSTNAME'] +'\n'))"]
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: pipy
  name: pipy
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: pipy
EOF

然后查看 Deployment 的信息,并没有任何副本运行。

$kmd get deployment
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
pipy   0/2     0            0           20s

通过 describe 命令发现如下警告,提示没有匹配资源的策略。Karmada 需要根据资源的多集群调度策略 PropagationPolicy 将其调度到目标集群。

Warning  ApplyPolicyFailed  47s   resource-detector  No policy match for resource

Propagation 策略

应用下面的策略将 DeploymentService 资源调度到三个成员集群。

$kmd apply -f - <<EOF
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: pipy-propagation
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
      name: pipy
    - apiVersion: v1
      kind: Service
      name: pipy
  placement:
    clusterAffinity:
      clusterNames:
        - k3d-cluster-1
        - k3d-cluster-2
        - k3d-cluster-3
    replicaScheduling:
      replicaDivisionPreference: Weighted
      replicaSchedulingType: Divided
      weightPreference:
        staticWeightList:
          - targetCluster:
              clusterNames:
                - k3d-cluster-1
            weight: 10
          - targetCluster:
              clusterNames:
                - k3d-cluster-2
            weight: 10
          - targetCluster:
              clusterNames:
                - k3d-cluster-3
            weight: 10
EOF

在集群 cluster-1cluster-2 中可以看到有 pod 在运行。

$k1 get deployment
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
pipy   1/1     1            1           13s
$k2 get deployment
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
pipy   1/1     1            1           13s

cluster-3 上只找到了 deployment,但期望副本数为 0。这是因为在控制平面中创建的 deployment 只设置了两个副本。

$k3 get deployment
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
pipy   0/0     0            0           16s

在控制平面中将副本数调整为 3

$kmd scale deploy pipy --replicas 3

然后在 cluster-3 就可以看到第三个副本。

$k3 get po
NAME                   READY   STATUS    RESTARTS   AGE
pipy-8ff5f5987-5nqqw   1/1     Running   0          54s

部署客户端应用

接下来部署客户端应用。

$kmd apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: curl
  name: curl
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: curl
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: curl
    spec:
      containers:
      - image: curlimages/curl
        name: curl
        command: ["sleep", "365d"]
EOF

同样配置多集群调度策略,只调度到集群 cluster-3

$kmd apply -f - <<EOF
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: curl-propagation
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
      name: curl
  placement:
    clusterAffinity:
      clusterNames:
        - k3d-cluster-3
    replicaScheduling:
      replicaDivisionPreference: Weighted
      replicaSchedulingType: Divided
      weightPreference:
        staticWeightList:
          - targetCluster:
              clusterNames:
                - k3d-cluster-3
            weight: 1
EOF

从该 pod 中发起请求访问应用的 8080 端口,成功收到响应。多次请求,都会获得同样的结果。

curl_client=`$k3 get po -l app=curl -o jsonpath='{.items[0].metadata.name}'`
$k3 exec $curl_client -- curl -s http://pipy.default:8080
pipy-8ff5f5987-5nqqw

总结

Karmada 的出现在不改变部署方式的情况下解决了多集群中应用的管理难题,实现了应用在多 Kubernetes 集群中的部署、同步和调度。文中没有展示的还有可观察性,Karmada 控制平面可以汇总成员集群中的监控数据并集中展示。

思考

可能你也发现了,在演示中客户端收到的响应都是来自同集群中的应用。

此时假如将 deployment 的副本数缩减到 2,并再次请求。你会发现请求失败,请求没有被调度到其他集群。

可见,虽然应用的跨集群管理由 Karmada 帮我们完成了,但是流量仍无法跨集群。这说明什么?当你的应用在做多集群部署时,由于应用间彼此依赖需要做近乎全量的部署,那影响和成本可想而知。

在下一篇,我将带大家来尝试来解决这个问题。

(转载本站文章请注明作者和出处乱世浮生,请勿用于任何商业用途)

comments powered by Disqus