1. 程式人生 > >使用 Ansible 管理 Kubernetes 資源

使用 Ansible 管理 Kubernetes 資源

前兩天,一篇「Think twice before using Helm[1]」(譯文:「恕我直言,對Helm大家還是要三思而後用」) 引起了大家的關注。作者從認證,生命週期管理,錯誤處理等多個角度說明了 Helm 自身的問題。我基本贊同作者的觀點。多數情況下我們只是把 helm 當做一個模板引擎在使用,把 charts 生成 Kubernetes 可以處理的格式。但是從使用角度來說,這個模板實現的太重了。有興趣的可以去讀讀原文。

那如果 Helm 不輕量好用的話,我們有啥其他選擇?

Ansible 做為部署管理的工具,正在受到越來越多的運維人員的追捧。他支援 Jinja2 的模板引擎,而且是無代理節點的架構,很方便來做一些模板工作。所以本文來介紹使用 Ansible 如何管理 Kubernetes 上面的資源。

首先使用 Ansible 避免不了使用其模組。與 Kubernetes 相關的模組可以從[2]找到。現在主要有k8sk8s_factsk8s_scalekubernetesoc 5個模組。其中 kubernetes 和 oc 模組因為實現邏輯不好用,在 ansible 2.6 版本中已經廢棄掉, 推薦使用前三個。其中,k8s_scale 來自 ansible 2.5, k8s 來自 ansible 2.6, k8s_facts 來自 ansible 2.7。使用這三個模組的話,還需要安裝 openshift

 的 Python 包。以下程式碼全部基於 ansible 2.7 版本。

一、k8s 模組

管理 kubernetes 各種資源的話,使用 k8s 模組就可以了,如下是建立 namespace 的寫法

- name: Create a k8s namespace
  k8s:
    name: testing
    api_version: v1
    kind: Namespace
    state: present

如果要建立一個 Service, 也可以使用如下面的寫法。

- name: Create a Service object from an inline definition
  k8s:
    state: present
    definition:
      apiVersion: v1
      kind: Service
      metadata:
        name: web
        namespace: testing
        labels:
          app: galaxy
          service: web
      spec:
        selector:
          app: galaxy
          service: web
        ports:
          - protocol: TCP
            targetPort: 8000
            name: port-8000-tcp
            port: 8000

可以看到 definition 裡面就是原生的 Kubernetes 裡面的寫法,而 k8s 模組的引數也寫少,所以上手會很快。

如果 k8s 模組和 ansible lookup 外掛合用的話,可以寫出更加簡潔的程式碼,如下

# tasks.yml
- name: Create a Service object from an external file
  var:
    name: "web"
  k8s:
    state: present
    definition:
      {{ lookup('template', '/path/to/service.yml') | to_yaml(indent=6) }}
# /path/to/service.yml
---
apiVersion: v1
kind: Service
metadata:
  name: {{ name }}          # <-- 這裡可以使用變數
namespace: testing
labels:
  app: galaxy
  service: web
spec:
  selector:
    app: galaxy
    service: web
ports:
  - protocol: TCP
    targetPort: 8000
    name: port-8000-tcp
    port: 8000

可以看到 Kubernetes service 檔案可以完全從 task 裡面獨立出來,獨立後的寫法就是原生的 kubernetes 的格式,基本就和 Charts 的結構差不多了。

基於此,完全可以使用這種方式替換掉 helm 的模板功能,而且沒有引入任何額外的依賴,就是直接的 ansible 生成相關檔案,丟給 kubernetes api 來處理。等部署完成後,我們也可以脫離 Ansible 繼續通過 kubelet 命令維護這些資源。也正是由於這麼簡潔的實現,k8s 模組可以管理 Kubernetes 和 OpenShift, 也可以管理各種 CRD 資源。

相比於 helm , 這種方法的缺點在於 YAML 檔案都要自己寫,沒有社群在維護的 Charts。不像 helm 那樣,一個命令就可以把服務都安裝上。前期的工作還是挺多的。但是從另外一個角度來說,社群維護的 Charts 做一些 Demo 還可以,真要生產上面使用,還是要做大量工作的。所以從這個角度上講,使用 Ansible 也沒有帶來太大的工作量。

我更期待社群可以使用 Ansible 直接管理 Charts 資源,或可以有一個工具把 Charts 的 Go 模板轉成 Ansible 可以接受的 Jinja2 格式。

二、k8s_scale 和 k8s_facts 模組

這兩個模組算輔助的功能,我覺得使用的機會可能並不會太多。k8s_scale 的例子如下:

- name: Scale deployment up, and extend timeout
  k8s_scale:
    api_version: v1
    kind: Deployment
    name: elastic
    namespace: myproject
    replicas: 3
    wait_timeout: 60

- name: Scale deployment down when current replicas match
  k8s_scale:
    api_version: v1
    kind: Deployment
    name: elastic
    namespace: myproject
    current_replicas: 3
    replicas: 2

它可以動態調整 Deployment 的 replicas 個數,基本上等同於kubectl scale 命令,但是這個功能基本可以使用 k8s 模組通過改變 replicas 引數來調整。

k8s_facts的例子如下:

- name: Get an existing Service object
  k8s_facts:
    api_version: v1
    kind: Service
    name: web
    namespace: testing
  register: web_service

使用他,你可以檢查某個資源是否存在,如果存在的話,還可以獲得這個資源的yaml 檔案描述,我覺得在 Ansible 流程控制中會有一些作用,可以根據當前 Kubernets 裡面資源情況,有選擇的做一些動作。

三、小技巧

因為 Ansible 是 Python 編寫的,在使用 pip 安裝時容易破壞系統已經安裝的 Python 包,推薦使用虛擬環境來安裝。

# mkvirtualenv --system-site-packages ansible
# pip install 'ansible<2.7' openshift

使用時,需要指定 Ansible 使用的 python interpreter 變數

# workon ansible
# ansible-playbook -i localhost, -c local test.yml  -e ansible_python_interpreter=${VIRTUAL_ENV}/bin/python

四、demo

以下是使用 ansible 在 OpenShift 上面部署 echoserver 的一個完整例子

---
- hosts: localhost
  connection: local
  gather_facts: false
  vars:
    namespace: demo
  tasks:
    - name: Create echo server deployment config
      k8s:
        namespace: "{{ namespace }}"
        definition:
          apiVersion: v1
          kind: DeploymentConfig
          metadata:
            name: echoserver
          spec:
            replicas: 1
            template:
              metadata:
                labels:
                  app: echoserver
              spec:
                containers:
                  - name: echoserver
                    image: googlecontainer/echoserver:1.5
                    readnessProbe:
                      httpGet:
                        port: 8080
                        path: /
                      initialDelaySeconds: 20
                      periodSeconds: 5
                    livenessProbe:
                      httpGet:
                        port: 8080
                        path: /
                      initialDelaySeconds: 10
                      periodSeconds: 3
    - name: Create echo server service
      k8s:
        namespace: "{{ namespace }}"
        definition:
          apiVersion: v1
          kind: Service
          metadata:
            name: echoserver
          spec:
            ports:
              - name: http
                port: 8080
                targetPort: 8080
            selector:
              app: echoserver
    - name: Create echo server router
      k8s:
        namespace: "{{ namespace }}"
        definition:
          kind: Route
          apiVersion: route.openshift.io/v1
          metadata:
            name: echoserver
          spec:
            host: echoserver.local
            to:
              kind: Service
              name: echoserver
            port:
              targetPort: http

參考文獻:

[1]https://medium.com/virtuslab/think-twice-before-using-helm-25fbb18bc822

[2]https://docs.ansible.com/ansible/latest/modules/list_of_clustering_modules.html