- 因為程式跟文章都是當天寫(想),有錯字、語病跟問題請大家在留言給我。
 - 範例會放在 Controller 101 維護。
 
在動手實作 Kubernetes 自定義控制器 Part5 文章結束後,基本上已經完成了這個自定義控制器範例的功能,這時若我們想要部署這個控制器的話,該怎麼辦呢?因為過去文章中,我們都是以 Go 語言指令直接建構程式進行測試,且使用 client-go 與 API Server 溝通時,都是以cluster-admin使用者來達成,這種作法如果是正式上線環境,必然會有很多疑慮,比如說控制器環境有安全問題,如果這些狀況被取得 Kubernetes cluster-admin 權限的話,就可能會危害到整個 Kubernetes 環境,因為 cluster-admin 可以操作任何 Kubernetes API 資源。基於這些問題,今天就是要來說明如何讓控制器正確的部署到 Kubernetes 叢集中執行。
由於自定義控制器部署到 Kubernetes 叢集時,需要擁有特定使用者與權限操作 Kubernetes APIs,才能正常的執行控制器功能,因此必須利用 Service Account 與 RBAC 來讓控制器能在 Pod 中與 API Server 溝通。
雖然 Kubernetes 在建立 Namespace 時,預設也會自動建立一個名稱為
default的 Service Account,但這個 Service Account 通常會被用於該 Namespace 下的所有 Pod,因此不建議將 RBAC 權限賦予給這個 Service Account。
而要提供部署到 Kubernetes 叢集中,通常需要準備以下幾個檔案:
├── Dockerfile # 將控制器建構成容器映像檔
└── deploy # 提供部署控制器的目錄
    ├── crd.yml # 自定義控制器的 CRDs
    ├── deployment.yml # 用於部署自定義控制器程式本身
    ├── rbac.yml # 自定義控制器的 API 存取權限
    └── sa.yml # 自定義控制器的服務帳戶,會與 RBAC 結合以限制 Pod 存取 API 的權限
由於今天的實作內容需要用到 Kubernetes 與 Docker,因此須完成以下需求與條件:
minikube start --kubernetes-version=v1.15.4。eval $(minikube docker-env)來取的 Docker 參數,並遠端操作。本部分將建立這些檔案,並執行所需的操作。首先由於要將控制器部署到 Kubernetes 中,因此必須將控制器程式建構成容器映像檔,這樣才能透過 Pod 的形式來部署。而建構映像檔可以透過 Dockerfile 來達到,如以下內容:
FROM kairen/golang-dep:1.12-alpine AS build
ENV GOPATH "/go"
ENV PROJECT_PATH "$GOPATH/src/github.com/cloud-native-taiwan/controller101"
ENV GO111MODULE "on"
COPY . $PROJECT_PATH
RUN cd $PROJECT_PATH && \
  make && mv out/controller /tmp/controller
FROM alpine:3.7
COPY --from=build /tmp/controller /bin/controller
ENTRYPOINT ["controller"]
完成後,即可透過 Docker 指令來建構與推送到公有的 Container registry 上以便後續部署使用:
$ eval $(minikube docker-env)
$ docker build -t <owner>/controller101:v0.1.0 .
$ docker push <owner>/controller101:v0.1.0
接著我們將新增以下檔案用於部署控制器到 Kubernetes 上執行。
用於以 CRD API 來新增自定義資源的檔案。在這檔案中,通常會定義一個或多個自定義資源內容。
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: virtualmachines.cloudnative.tw
spec:
  group: cloudnative.tw
  version: v1alpha1
  names:
    kind: VirtualMachine
    singular: virtualmachine
    plural: virtualmachines
    shortNames:
    - vm
    - vms
  scope: Namespaced
  additionalPrinterColumns:
  - name: Status
    type: string
    JSONPath: .status.phase
  - name: CPU
    type: number
    JSONPath: .status.server.usage.cpu
  - name: Memory
    type: number
    JSONPath: .status.server.usage.memory
  - name: Age
    type: date
    JSONPath: .metadata.creationTimestamp
有些自定義控制器會直接透過 apiextensions-apiserver 在程式啟動時,將自定義資源自動新增到當前 Kubernetes 叢集中。
該檔案會定義一個 Service Account 用於提供給控制器程式在 Pod 中與 API Server 溝通的帳戶,這個帳戶會透過 RBAC 賦予特定的權限,以便控制器程式獲取操作所需 API 資源的權限。
apiVersion: v1
kind: ServiceAccount
metadata:
  name: controller101
  namespace: kube-system
該檔案會賦予指定 Service Account 相對應的 API 權限,由於這個自定義控制器需要對 VirtualMachines 資源做各種操作(如 Create、Update、Delete、Get 與 Watch 等等),因此需要設定相對應的 API Group 與 Resources 來確保控制器程式能獲取權限。
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: controller101-role
rules:
- apiGroups:
  - cloudnative.tw
  resources:
  - "virtualmachines"
  verbs:
  - "*"
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: controller101-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: controller101-role
subjects:
- kind: ServiceAccount
  namespace: kube-system
  name: controller101
如果控制器需要存取其他 API 時,就必須在
rules欄位額外新增。請務必確保給予最小可運作的權限。
該檔案定義自定義控制器的容器如何在 Kubernetes 部署,我們會在以 Deployment 方式進行部署,因為能利用 Deployment 機制來確保控制器在叢集中的可靠性。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: controller101
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: controller101
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        k8s-app: controller101
    spec:
      priorityClassName: system-cluster-critical # 由於控制器有可能是重要元件,因此要確保節點資源不足時,不會優先被驅逐
      tolerations:
      - key: CriticalAddonsOnly
        operator: Exists
      - effect: NoSchedule
        key: node-role.kubernetes.io/master
      serviceAccountName: controller101
      containers:
      - name: controller
        image: kairen/controller101:v0.1.0
        env:
        - name: DOCKER_HOST
          value: "tcp://192.168.99.159:2376"
        - name: DOCKER_TLS_VERIFY
          value: "1"
        - name: DOCKER_CERT_PATH
          value: "/etc/docker-certs"
        args:
        - --v=2
        - --logtostderr=true
        - --vm-driver=docker
        - --leader-elect=false
        volumeMounts:
        - name: docker-certs
          mountPath: "/etc/docker-certs"
          readOnly: true
      volumes:
      - name: docker-certs
        secret:
          secretName: docker-certs
- 其中
 env部分,需要依據環境差異來改變。- 若有多個副本時,需要透過
 --leader-elect來啟用 Leader Election 機制。
當檔案都建立好後,就可以透過 kubectl 來部署到 Kubernetes 叢集。首先由於範例使用 Docker Driver 來測試,因此需要在控制器程式啟動時載入 Docker Certs,故這邊要先將這些檔案以 Secrets 方式存到 Kubernetes 中,之後再提供給 Deployment 掛載使用。
$ minikube docker-env
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.159:2376"
export DOCKER_CERT_PATH="/Users/test/.minikube/certs"
$ kubectl -n kube-system create secret generic docker-certs \
    --from-file=$HOME/.minikube/certs/ca.pem \
    --from-file=$HOME/.minikube/certs/cert.pem \
    --from-file=$HOME/.minikube/certs/key.pem
建立好 Docker certs 後,就可以執行以下指令進行部署:
$ kubectl apply -f deploy/
customresourcedefinition.apiextensions.k8s.io/virtualmachines.cloudnative.tw created
deployment.apps/controller101 created
clusterrole.rbac.authorization.k8s.io/controller101-role created
clusterrolebinding.rbac.authorization.k8s.io/controller101-rolebinding created
serviceaccount/controller101 created
$ kubectl -n kube-system logs -f controller101-7858db7484-5bjvf
I1015 11:14:45.159556       1 controller.go:77] Starting the controller
I1015 11:14:45.159640       1 controller.go:78] Waiting for the informer caches to sync
I1015 11:14:45.262042       1 controller.go:86] Started workers
若 Minikube 的 Docker envs 資訊不同時,需要修改
deploy/deployment.yml裡面 envs。
接著嘗試建立一個 VirtualMachine 資源實例,並用 kubectl get 與 docker ps 查看狀態:
$ cat <<EOF | kubectl apply -f -
apiVersion: cloudnative.tw/v1alpha1
kind: VirtualMachine
metadata:
  name: test-vm
spec:
  resource:
    cpu: 2
    memory: 4G
EOF
virtualmachine.cloudnative.tw/test-vm created
$ kubectl get vms
NAME      STATUS   CPU   MEMORY                AGE
test-vm   Active   0     0.11359797976548552   22s
$ docker ps --filter "name=test-vm"
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
5ac009171998        nginx:1.17.4        "nginx -g 'daemon of…"   46 seconds ago      Up 45 seconds       80/tcp              test-vm
從今天實作中,可以了解到部署自定義控制器到 Kubernetes 叢集並非難事,一但控制器容器化後,並以 Kubernetes API 資源形式部署時,就能夠增加控制器的可攜帶性與維護性,往後有版本更新時,也可以利用 Kubernetes 的一些機制(如 Rolling Upgrade)來安全地更新控制器程式。
最後由於鐵人賽文章不夠用,因此關於 CKA/CKAD 認證、 Admission Controller 等文章,都會在 KaiRen's Blog 上持續新增。