1. 程式人生 > >在阿里雲上部署生產級別Kubernetes叢集_Kubernetes中文社群

在阿里雲上部署生產級別Kubernetes叢集_Kubernetes中文社群

阿里雲是國內非常受歡迎的基礎雲平臺,隨著Kubernetes的普及,越來越多的企業開始籌劃在阿里雲上部署自己的Kubernetes叢集。 本文將結合實戰中總結的經驗,分析和歸納一套在阿里雲上部署生產級別Kubernetes叢集的方法。 文中所採取的技術方案具有一定的主觀性,供各位讀者參考。在實踐中可以根據具體使用場景進行優化。

目標

當我們剛接觸Kubernetes進行測試叢集的搭建時,往往會選擇一篇已有的教程,照著教程完成叢集搭建。我們很少去質疑教程作者每一步操作的合理性, 只想快點把叢集搭建起來,早點開始實際的上手體驗。

與測試叢集不同,對於生產級別的部署,我們會有更加嚴格的要求,更加強調合理的規劃以及每個步驟的合理性以及可管理性。以下是我們設定的目標:

  • 沒有單點故障。任何一臺伺服器離線不會影響到整個叢集的正常運轉
  • 充分利用阿里雲原生提供的SLB,雲盤等工具,支援Volume掛載,LoadBalancer型別的Service等Kubernetes基礎功能。 獲得完整的Kubernetes使用體驗。
  • 在不犧牲安全性和穩定性的前提下,儘可能降低日常運維所需要的投入,將可以由程式完成的重複性工作自動化
  • 支援隨著業務規模擴充套件的動態叢集規模擴充套件

因為篇幅的原因,以下內容將不作為本文的目標,留待日後再做分享:

  • 叢集執行成本控制
  • 監控、日誌等運維繫統的搭建
  • 安全防護以及許可權設計

現狀

目前Kubernetes主要支援的雲平臺還是海外的幾大主流平臺,但是對比阿里雲提供的基礎設施,我們已經具有了基本相同的底層環境。 阿里雲提供的VPC,雲盤,SLB,NAS等元件,都為搭建生成級別Kubernetes叢集提供了很好的支援。充分利用這些元件, 我們應該可以搭建出完整可用的Kubernetes叢集。但是仔細研究Kubernetes的程式碼我們會發現,

阿里雲的CloudProvider暫時沒有被合併到上游當中, 所以我們需要設計方案來解決Kubernetes暫時還沒有原生阿里雲支援的問題。

Kubernetes生態圈發展迅速,目前已經有了像kops這種叢集自動建立工具幫助運維工程師建立叢集。但是目前已有的叢集建立工具均只支援國外的雲平臺, 使得國內雲平臺的使用者只能採取手動搭建的辦法。像kubeadm這種半自動工具還可以使用,也算是減輕了不少負擔。從目前狀況來看, 運維的負擔依然很嚴重,為我們實現生產級別部署所追求的自動化、規模化的目標帶來了不小的障礙。

由於網路原因造成的映象拉取困難也給我們建立Kubernetes叢集製造了不小的麻煩,好在阿里雲一直在致力於解決這個問題,為使用者提供了

映象拉取加速服務以及重要映象的Mirror

另一個問題是作業系統映象的問題,在後面分析作業系統的時候再詳細展開。

架構

基於消除單點故障以及降低複雜度的考慮,我們設計了由5臺伺服器,分兩個伺服器組構成的Kubernetes叢集的控制節點,並視業務需求情況由N臺伺服器, 分多個伺服器組,構成叢集的執行節點。如下圖所示:

在設計這一架構時,我們考慮了以下幾點:

  • 整個叢集的主要構成元素為伺服器組。一組伺服器具有相同的硬體配置,服務相同的功能,在軟體配置上也基本相同。這樣為伺服器的自動化管理打下了很好的基礎。
  • 由3臺伺服器組成的etcd叢集,在其中任何一臺伺服器離線時,均可以正常工作。為整個Kubernetes叢集的資料持久化儲存提供了穩定可靠的基礎
  • 2臺同時執行著Kubernetes核心元件kube-apiserver,kube-controller-manager,kube-scheduler的伺服器,為整個叢集的控制平面提供了高可用性。
  • 多個執行節點伺服器組,有著不同的CPU,記憶體,磁碟配置。讓我們可以靈活的根據業務對執行環境的要求來選擇不同的伺服器組。

叢集搭建

在有了架構藍圖後,接下來讓我們來實際搭建這個叢集。

作業系統選型

搭建叢集首先會面臨的問題是,選什麼配置的伺服器,用什麼作業系統。伺服器硬體配置相對好解決,控制節點在業務量不大的時候選擇入門級別的配置再隨著業務增長不斷提升即可, 執行節點應當根據業務需要來選擇,可能要做一些嘗試才能定下來最適合的硬體配置。比較困難的選擇是作業系統的選型。

只要是使用較新的Kernel的Linux主機,均可以用來執行Kubernetes叢集,但是發行版的選擇卻需要從多個方面來考慮。在這裡我們選擇了CoreOS作為最基礎的作業系統。 做出這一選擇是基於以下這些因素:

  • CoreOS是專門為執行容器設計的作業系統,非常適合用來執行Kubernetes叢集
  • CoreOS去除了包管理,使用映象升級的方式,大大簡化了運維的複雜度
  • CoreOS可以使用cloud-init,方便的對伺服器進行初始化
  • 阿里雲提供了CoreOS的支援

CoreOS的詳細介紹,大家可以參考官方的文件,在這裡就不展開了。需要指出的是阿里雲提供的CoreOS映象版本較低,需要先進行升級才能正常使用,增加了不少麻煩。 希望以後阿里雲能夠提供最新版本的CoreOS映象,改善這一問題。

CoreOS版本升級

由於網路的原因,CoreOS在國內不能正常進行升級。我們需要在國內搭建升級伺服器。CoreRoller是一個可選項。具體的搭建可以參考相關文件,在這裡就略過了。

在順利搭建好升級伺服器之後,可以修改/etc/coreos/update.conf,新增SERVER=https://YOUR_SERVER/v1/update/這一條配置,然後使用以下指令來升級伺服器:

sudo systemctl restart update-engine
update_engine_client -update

我們搭建了自己的升級伺服器,如果有需要的朋友可以聯絡我們獲得伺服器地址。後面所有啟動的CoreOS伺服器,我們均假設管理員已經提前完成了版本升級的工作, 在流程中不再重複。如果阿里雲開始提供最新的CoreOS映象,那這一步可以省略掉。

引入kube-aliyun解決相容問題

在前面分析現狀時,我們提到了阿里雲的CloudProvider暫時還未被併入Kubernetes,所以我們需要額外的工具來解決原生Kubernetes與阿里雲之間的相容問題。

針對這一問題,我們開發了一款名為kube-aliyun的工具。kube-aliyun以controller的形式執行在叢集內部,提供以下的功能:

  • 配置VPC路由,使得叢集內的Pod網路互通
  • 使用SLB支援LoadBalancer型別的Service
  • 使用flexv支援雲盤型別的Volume的動態掛載以及解除掛載

容器網路方案選型

Kubernetes要求所有叢集內部的Pod可以不經過NAT互訪,所以我們需要在伺服器網路之上再搭建一層容器網路。容器網路的實現方案有多種,比如常見的flannel,calico等。 在這裡我們選擇了更加簡單的kubenet +hostroutes方案。hostroutes是我們專門配合kubenet開發的路由配置工具, 詳細的資訊可以參考它的Github主頁,以及這篇文件

如果叢集規模較小,我們還可以使用kube-aliyun的VPC路由配置功能。這樣主機上不用對路由做任何的配置,所有的網路路由交給了VPC來完成,也不失為一種簡單易用的方案。

SSL證書管理

SSL證書和配置是使用Kubernetes過程中非常容易出問題的點。這裡推薦使用cfssl來做證書的生成和管理,主要看重了cfssl簡單易用的特點,比起openssl更加容易操作和自動化。 cfssl的使用方法請參考官方的文件,這裡不再重複。

在Kubernetes叢集當中,我們一共需要生成4種類型的證書。另外etcd也可以通過證書進行驗證和保護。出於複雜度考慮今天暫時不使用。

API Server證書

API Server證書主要用於客戶端程式連線apiserver時進行加密和驗證。可以使用以下模板作為CSR,填入相應的引數後生成:

{
    "CN": "${CLUSTER_NAME}",
    "hosts": [
        "kubernetes",
        "kubernetes.default",
        "kubernetes.default.svc",
        "kubernetes.default.svc.cluster.local",
        "10.3.0.1",
        "${SERVER_PRIVATE_IP}",
        "${SERVER_PUBLIC_IP}",
        "${LOAD_BALANCER_IP}"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

之後將以${APISERVER_PEM}和${APISERVER_KEY}分別表示生成出的證書和私匙。

kubelet證書

kubelet證書用於系統元件訪問kubelet內建的HTTP Server,獲取執行狀態或者呼叫kubelet提供的功能時進行加密和驗證。CSR模板如下:

{
    "CN": "${SERVER_NAME}",
    "hosts": [
        "${SERVER_NAME}",
        "${SERVER_PRIVATE_IP}",
        "${SERVER_PUBLIC_IP}"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

之後將以${SERVER_PEM}和${SERVER_KEY}分別表示生成出的證書和私匙。

Service Account證書

Service Account證書用於生成各個Namespace預設的token,以及進行token驗證。在叢集內部服務訪問API Server時會使用這個token進行身份認證。CSR模板如下:

{
    "CN": "service-account",
    "hosts": [
        "service-account"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

之後將以${SERVICEACCOUNT_PEM}和${SERVICEACCOUNT_KEY}分別表示生成出的證書和私匙。

kubectl證書

kubectl證書用於管理員或者使用者遠端訪問叢集,下達各種指令時的身份認證。CSR模板如下:

{
    "CN": "${USERNAME}",
    "hosts": [
        "${USERNAME}"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

在建立叢集時並不需要這一證書,但是在叢集建立完成後需要為所有使用者生成證書,才能配置好本地的kubectl,獲得訪問叢集的許可權。

建立VPC

在阿里雲控制檯介面,可以很方便的建立VPC,選擇目標可用區,並建立伺服器所在網段即可。這裡我們用10.99.0.0/24這個網段,讀者可以根據自身業務設計選擇適合的網段。 這裡我們選擇的網段,除去阿里雲提供的服務以及內網SLB佔掉的IP地址,至少有200個以上的空餘地址,足以滿足絕大部分場景下的規模需要。

搭建etcd叢集

CoreOS對etcd有原生的支援,我們可以使用CoreOS官方提供的discovery服務快速的完成etcd叢集的搭建。

首先訪問https://discovery.etcd.io/new?size=3,將得到的地址以及伺服器IP地址填入以下檔案當中:

#cloud-config

coreos:
  etcd2:
    discovery: "https://discovery.etcd.io/<token>"
    advertise-client-urls: "http://${SERVER_PRIVATE_IP}:2379"
    initial-advertise-peer-urls: "http://${SERVER_PRIVATE_IP}:2380"
    listen-client-urls: "http://0.0.0.0:2379"
    listen-peer-urls: "http://${SERVER_PRIVATE_IP}:2380"
  units:
    - name: "start-etcd.service"
      command: "start"
      enable: true
      content: |-
        [Service]
        Type=oneshot
        ExecStart=/usr/bin/systemctl start etcd2
        [Install]
        WantedBy=multi-user.target

接下來在VPC中建立3臺伺服器,並在伺服器上建立檔案cloud-init.yaml包含上面的內容。再使用coreos-cloudinit -from-file cloud-init.yaml對伺服器進行初始化。

一切順利的話,各臺伺服器上的etcd服務將找到其他的節點,共同組成一個高可用的etcd叢集。

搭建Master伺服器組

在Master伺服器組上,我們將在每臺伺服器上通過kubelet執行kube-aliyun、kube-controller-manager、kube-scheduler這幾個元件。 為了簡化配置流程,我們依然使用cloud-init來進行伺服器初始化。將etcd伺服器組的內網IP填入以下檔案:

#cloud-config

coreos:
  units:
    - name: "docker.service"
      drop-ins:
        - name: "50-docker-opts.conf"
          content: |
            [Service]
            Environment=DOCKER_OPTS='--registry-mirror="https://${YOUR_MIRROR}.mirror.aliyuncs.com"'
    - name: "kubelet.service"
      command: "start"
      enable: true
      content: |-
        [Service]
        Environment=KUBELET_VERSION=v1.5.1_coreos.0
        Environment=KUBELET_ACI=kubeup.com/aci/coreos/hyperkube
        Environment="RKT_OPTS=--uuid-file-save=/var/run/kubelet-pod.uuid \
          --trust-keys-from-https \
          --volume dns,kind=host,source=/etc/resolv.conf \
          --mount volume=dns,target=/etc/resolv.conf \
          --volume var-log,kind=host,source=/var/log \
          --mount volume=var-log,target=/var/log \
          --volume lib-modules,kind=host,source=/lib/modules \
          --mount volume=lib-modules,target=/lib/modules"
        ExecStartPre=/usr/bin/systemctl stop update-engine
        ExecStartPre=/usr/bin/mkdir -p /etc/kubernetes/manifests
        ExecStartPre=/usr/bin/mkdir -p /var/log/containers
        ExecStartPre=-/usr/bin/rkt rm --uuid-file=/var/run/kubelet-pod.uuid
        ExecStart=/usr/lib/coreos/kubelet-wrapper \
          --api_servers=http://localhost:8080 \
          --register-schedulable=false \
          --allow-privileged=true \
          --config=/etc/kubernetes/manifests \
          --cluster-dns=10.3.0.10 \
          --node-ip=${SERVER_PRIVATE_IP} \
          --hostname-override=${SERVER_PRIVATE_IP} \
          --cluster-domain=cluster.local \
          --network-plugin=kubenet \
          --tls-cert-file=/etc/kubernetes/ssl/server.pem \
          --tls-private-key-file=/etc/kubernetes/ssl/server-key.pem \
          --pod-infra-container-image=registry.aliyuncs.com/archon/pause-amd64:3.0
        ExecStop=-/usr/bin/rkt stop --uuid-file=/var/run/kubelet-pod.uuid
        Restart=always
        RestartSec=10
        User=root
        [Install]
        WantedBy=multi-user.target
write_files:
  - path: "/etc/kubernetes/manifests/kube-apiserver.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-apiserver
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-apiserver
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - apiserver
          - --bind-address=0.0.0.0
          - --etcd-servers=http://${ETCD_SERVER1_IP}:2379,http://${ETCD_SERVER2_IP}:2379,http://${ETCD_SERVER3_IP}:2379
          - --allow-privileged=true
          - --service-cluster-ip-range=10.3.0.0/24
          - --runtime-config=extensions/v1beta1=true,extensions/v1beta1/thirdpartyresources=true
          - --secure-port=443
          - --advertise-address=${LOAD_BALANCER_IP}
          - --admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
          - --tls-cert-file=/etc/kubernetes/ssl/apiserver.pem
          - --tls-private-key-file=/etc/kubernetes/ssl/apiserver-key.pem
          - --service-account-key-file=/etc/kubernetes/ssl/serviceaccount-key.pem
          - --client-ca-file=/etc/kubernetes/ssl/ca.pem
          ports:
          - containerPort: 443
            hostPort: 443
            name: https
          - containerPort: 8080
            hostPort: 8080
            name: local
          volumeMounts:
          - mountPath: /etc/kubernetes/ssl
            name: ssl-certs-kubernetes
            readOnly: true
          - mountPath: /etc/ssl/certs
            name: ssl-certs-host
            readOnly: true
        volumes:
        - hostPath:
            path: /etc/kubernetes/ssl
          name: ssl-certs-kubernetes
        - hostPath:
            path: /usr/share/ca-certificates
          name: ssl-certs-host
  - path: "/etc/kubernetes/manifests/kube-proxy.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-proxy
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-proxy
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - proxy
          - --master=http://127.0.0.1:8080
          - --proxy-mode=iptables
          securityContext:
            privileged: true
          volumeMounts:
          - mountPath: /etc/ssl/certs
            name: ssl-certs-host
            readOnly: true
        volumes:
        - hostPath:
            path: /usr/share/ca-certificates
          name: ssl-certs-host
  - path: "/etc/kubernetes/manifests/kube-controller-manager.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-controller-manager
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-controller-manager
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - controller-manager
          - --master=http://127.0.0.1:8080
          - --leader-elect=true
          - --service-account-private-key-file=/etc/kubernetes/ssl/serviceaccount-key.pem
          - --root-ca-file=/etc/kubernetes/ssl/ca.pem
          - --allocate-node-cidrs=true
          - --cluster-cidr=10.2.0.0/16
          - --configure-cloud-routes=false
          livenessProbe:
            httpGet:
              host: 127.0.0.1
              path: /healthz
              port: 10252
            initialDelaySeconds: 15
            timeoutSeconds: 1
          volumeMounts:
          - mountPath: /etc/kubernetes/ssl
            name: ssl-certs-kubernetes
            readOnly: true
          - mountPath: /etc/ssl/certs
            name: ssl-certs-host
            readOnly: true
        volumes:
        - hostPath:
            path: /etc/kubernetes/ssl
          name: ssl-certs-kubernetes
        - hostPath:
            path: /usr/share/ca-certificates
          name: ssl-certs-host
  - path: "/etc/kubernetes/manifests/kube-scheduler.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-scheduler
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-scheduler
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - scheduler
          - --master=http://127.0.0.1:8080
          - --leader-elect=true
          livenessProbe:
            httpGet:
              host: 127.0.0.1
              path: /healthz
              port: 10251
            initialDelaySeconds: 15
            timeoutSeconds: 1
  - path: "/etc/kubernetes/manifests/kube-aliyun.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: aliyun-controller
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: aliyun-controller
          image: registry.aliyuncs.com/kubeup/kube-aliyun
          command:
          - /aliyun-controller
          - --server=http://127.0.0.1:8080
          - --leader-elect=true
          - --cluster-cidr=10.2.0.0/16
          env:
          - name: ALIYUN_ACCESS_KEY
            valueFrom:
              secretKeyRef:
                name: aliyun-creds
                key: accessKey
          - name: ALIYUN_ACCESS_KEY_SECRET
            valueFrom:
              secretKeyRef:
                name: aliyun-creds
                key: accessKeySecret
          - name: ALIYUN_REGION
            value: ${YOUR_VPC_REGION}
          - name: ALIYUN_VPC
            value: ${YOUR_VPC_ID}
          - name: ALIYUN_ROUTER
            value: ${YOUR_ROUTER_ID}
          - name: ALIYUN_ROUTE_TABLE
            value: ${YOUR_ROUTE_TABLE_ID}
          - name: ALIYUN_VSWITCH
            value: ${YOUR_VSWITCH_ID}
  - path: "/etc/kubernetes/ssl/ca.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${CA_PEM}
  - path: "/etc/kubernetes/ssl/apiserver.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${APISERVER_PEM}
  - path: "/etc/kubernetes/ssl/apiserver-key.pem"
    permissions: "0600"
    owner: "root"
    content: |
      ${APISERVER_KEY}
  - path: "/etc/kubernetes/ssl/serviceaccount.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${SERVICEACCOUNT_PEM}
  - path: "/etc/kubernetes/ssl/serviceaccount-key.pem"
    permissions: "0600"
    owner: "root"
    content: |
      ${SERVICEACCOUNT_KEY}
  - path: "/etc/kubernetes/ssl/server.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${SERVER_PEM}
  - path: "/etc/kubernetes/ssl/server-key.pem"
    permissions: "0600"
    owner: "root"
    content: |
      ${SERVER_KEY}

接下來使用cloud-init在新建立的2臺伺服器上完成伺服器初始化。經過一定時間的映象拉取,所有元件將正常啟動,這時可以在主機上用kubectl來驗證伺服器的啟動狀態。

建立LoadBalancer

如果直接使用Master伺服器組當中的任何一臺伺服器,會存在單點故障。我們使用阿里雲控制檯建立一個內網的SLB服務,在兩臺伺服器之上提供一個穩定的負載均衡的apiserver。 具體的操作流程請參考相關阿里雲的文件,因為apiserver暴露在443埠,我們只需要配置443埠的負載均衡即可。

搭建Node伺服器組

Node伺服器組的初始化與Master伺服器組的初始化類似。我們可以一次性啟動N臺伺服器,然後在每臺伺服器上用以下配置進行初始化:

#cloud-config

coreos:
  units:
    - name: "docker.service"
      drop-ins:
        - name: "50-docker-opts.conf"
          content: |
            [Service]
            Environment=DOCKER_OPTS='--registry-mirror="https://${YOUR_MIRROR}.mirror.aliyuncs.com"'
    - name: "kubelet.service"
      command: "start"
      enable: true
      content: |-
        [Service]
        Environment=KUBELET_VERSION=v1.5.1_coreos.0
        Environment=KUBELET_ACI=kubeup.com/aci/coreos/hyperkube
        Environment="RKT_OPTS=--uuid-file-save=/var/run/kubelet-pod.uuid \
          --trust-keys-from-https \
          --volume dns,kind=host,source=/etc/resolv.conf \
          --mount volume=dns,target=/etc/resolv.conf \
          --volume var-log,kind=host,source=/var/log \
          --mount volume=var-log,target=/var/log \
          --volume lib-modules,kind=host,source=/lib/modules \
          --mount volume=lib-modules,target=/lib/modules"
        ExecStartPre=/usr/bin/systemctl stop update-engine
        ExecStartPre=/usr/bin/mkdir -p /etc/kubernetes/manifests
        ExecStartPre=/usr/bin/mkdir -p /var/log/containers
        ExecStartPre=-/usr/bin/rkt rm --uuid-file=/var/run/kubelet-pod.uuid
        ExecStart=/usr/lib/coreos/kubelet-wrapper \
          --api_servers=https://${APISERVER_ENDPOINT}:443 \
          --register-schedulable=true \
          --allow-privileged=true \
          --pod-manifest-path=/etc/kubernetes/manifests \
          --cluster-dns=10.3.0.10 \
          --node-ip=${SERVER_PRIVATE_IP} \
          --hostname-override=${SERVER_PRIVATE_IP} \
          --cluster-domain=cluster.local \
          --network-plugin=kubenet \
          --kubeconfig=/etc/kubernetes/node-kubeconfig.yaml \
          --tls-cert-file=/etc/kubernetes/ssl/server.pem \
          --tls-private-key-file=/etc/kubernetes/ssl/server-key.pem \
          --pod-infra-container-image=registry.aliyuncs.com/archon/pause-amd64:3.0
        ExecStop=-/usr/bin/rkt stop --uuid-file=/var/run/kubelet-pod.uuid
        Restart=always
        RestartSec=10
        User=root
        [Install]
        WantedBy=multi-user.target
write_files:
  - path: "/etc/kubernetes/manifests/kube-proxy.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-proxy
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-proxy
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - proxy
          - --master=https://${APISERVER_ENDPOINT}:443
          - --kubeconfig=/etc/kubernetes/node-kubeconfig.yaml
          securityContext:
            privileged: true
          volumeMounts:
          - mountPath: /etc/ssl/certs
            name: ssl-certs-host
            readOnly: true
          - mountPath: /etc/kubernetes/node-kubeconfig.yaml
            name: kubeconfig
            readOnly: true
          - mountPath: /etc/kubernetes/ssl
            name: etc-kube-ssl
            readOnly: true
        volumes:
        - hostPath:
            path: /usr/share/ca-certificates
          name: ssl-certs-host
        - hostPath:
            path: /etc/kubernetes/node-kubeconfig.yaml
          name: kubeconfig
        - hostPath:
            path: /etc/kubernetes/ssl
          name: etc-kube-ssl
  - path: "/etc/kubernetes/node-kubeconfig.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Config
      clusters:
      - name: local
        cluster:
          certificate-authority: /etc/kubernetes/ssl/ca.pem
      users:
      - name: kubelet
        user:
          client-certificate: /etc/kubernetes/ssl/server.pem
          client-key: /etc/kubernetes/ssl/server-key.pem
      contexts:
      - context:
          cluster: local
          user: kubelet
        name: kubelet-context
      current-context: kubelet-context
  - path: "/etc/kubernetes/ssl/ca.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${CA_PEM}
  - path: "/etc/kubernetes/ssl/server.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${SERVER_PEM}
  - path: "/etc/kubernetes/ssl/server-key.pem"
    permissions: "0600"
    owner: "root"
    content: |
      ${SERVER_KEY}

部署kube-aliyun與hostroutes

kube-aliyun的Pod已經在Master伺服器組的描述檔案中進行了定義,但是這個Pod會因為缺少必要的Secret無法啟動。 我們建立這個Secret,來啟用kube-aliyun:

kubectl create secret generic aliyun-creds --namespace=kube-system --from-literal=accessKey=${YOUR_ACCESS_KEY} --from-literal=accessKeySecret=${YOUR_ACCESS_KEY_SECRET}

hostroutes是以DaemonSet的形式進行部署的。使用以下定義檔案:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: hostrouts
  labels:
    app: hostroutes
spec:
  template:
    metadata:
      name: hostroutes
      labels:
        app: hostroutes
    spec:
      hostNetwork: true
      containers:
        - resources:
            requests:
              cpu: 0.20
          securityContext:
            privileged: true
          image: kubeup/hostroutes
          name: hostroutes
          command: [ "/hostroutes", "--in-cluster" ]

自動化部署和運維

經過前面的手工叢集搭建,我們可以發現明顯的重複模式。所有的伺服器我們均是使用coreos-cloudinit -from-file cloud-init.yaml這個指令完成初始化, 唯一不同的就是不同的伺服器組有不同的配置檔案模板。這時動手能力強的同學已經可以自己進行簡單的程式設計來簡化伺服器初始化流程了。

這種初始化模式是我們有意設計的,目的是為了使用程式來實現伺服器的自動化運維。我們將以上的實踐抽象成了一款叫做Archon的叢集管理系統, 方便使用者使用描述式的方法來建立和管理叢集,這樣一個指令就可以完成叢集擴容、升級這些日常運維工作,大大降低了運維工程師的工作負擔。

關於Archon系統,這裡就不詳細介紹了。有興趣的朋友可以訪問專案的Github地址https://github.com/kubeup/archon 瞭解更多的資訊。

總結

在本文中,我們首先分析了在阿里雲上部署生產級別Kubernetes所具有的優勢以及面臨的挑戰。接著演示了基於CoreOS,使用kube-aliyun以及hostroutes來解決目前存在問題的方法。 最後提出使用archon來進行自動化叢集建立以及運維的可能性。整個叢集設計充分考慮了生產環境的高可用性需求,同時也使得管理員在運維時可以將出故障的伺服器離線進行維護, 或者使用RollingUpdate的方法刪除老伺服器建立新伺服器來下發更新。

由於篇幅所限,部分步驟並沒有進行詳細的解說,讀者在閱讀時可以更加註重對原理和思路的理解。在理解了設計思路之後,大可採用自動化工具進行叢集的建立和運維來提升工作效率。

本文並沒有簡單羅列建立叢集過程所使用的全部指令,讓讀者可以一條一條剪下複製來照著操作,而是著重理清思路,幫助讀者更好的理解叢集搭建的工作細節。 在介紹叢集建立流程環節,我們使用了更加底層的用cloud-init的定義檔案進行伺服器初始化這一演示方式,以期讓讀者能夠清楚看到建立的所有配置檔案的內容, 而不是簡單依賴工具進行建立,導致出現故障時無法自主解決。文中使用的CoreOS,cfssl等工具可能對部分讀者來說比較陌生,在使用過程中大可使用熟悉的工具進行替換,只要整體思路保持一致即可。

實際在生產環境中部署時,還需要部署日誌和監控等系統元件,才能有效的對叢集進行運維和管理。這些執行在叢集之上的系統將留待以後的文章再做分享。

希望本文可以幫助更多的公司更好的在阿里雲上利用Kubernetes構建自己的底層架構體系。並在Kubernetes上搭建起自己的DevOps體系,提高整體的研發和執行效率。