〇 2021.02.20: APIトークンを指定した方法について追記
Ansibleのk8sモジュールを使って、Kubernetes上のリソースを操作してみる。
意外といままで試してなかった…というか実は5月頃に途中まで試したけど当時はPython2環境でpip
から入れたりして文書化に手間取ってお蔵入りしてた内容。
環境
pip install ansible
したAnsible 2.10環境
$ ansible --version ansible 2.10.2 config file = /home/zaki/src/ansible-sample/k8s/ansible.cfg configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /home/zaki/src/ansible-sample/venv/lib64/python3.6/site-packages/ansible executable location = /home/zaki/src/ansible-sample/venv/bin/ansible python version = 3.6.8 (default, Apr 2 2020, 13:34:55) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
KubernetesはローカルのDocker上に立てたkindのKubernetes v1.18クラスタ。
$ kubeconfig get node -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME helm-cluster-control-plane Ready master 7d1h v1.18.2 172.20.0.2 <none> Ubuntu 19.10 3.10.0-1127.19.1.el7.x86_64 containerd://1.3.3-14-g449e9269 helm-cluster-worker Ready <none> 7d1h v1.18.2 172.20.0.5 <none> Ubuntu 19.10 3.10.0-1127.19.1.el7.x86_64 containerd://1.3.3-14-g449e9269 helm-cluster-worker2 Ready <none> 7d1h v1.18.2 172.20.0.4 <none> Ubuntu 19.10 3.10.0-1127.19.1.el7.x86_64 containerd://1.3.3-14-g449e9269
(pip) openshiftパッケージ
openshift(>=0.6)が必要なので入れる。
$ pip install openshift
これが無いとエラーになる。
k8sモジュール
Kubernetes認証情報有り環境
まずは、kubectl create namespace
とか実行できるユーザー&Kubernetes設定で、Ansibleも実行できるという条件で以下実行。
(ansible-playbook
を実行するユーザーの環境変数$KUBECONFIG
か~/.kube/config
の設定内容でKubernetesリソース操作が可能である、という条件)
namespace作成
- hosts: localhost gather_facts: no tasks: - name: Create a k8s namespace community.kubernetes.k8s: name: testing api_version: v1 kind: Namespace state: present
このplaybookを使ってansible-playbook
を実行。
(venv) [zaki@cloud-dev k8s]$ ansible-playbook playbook.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [localhost] ************************************************************** TASK [Create a k8s namespace] ************************************************* changed: [localhost] PLAY RECAP ******************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
すると、namespaceが作成される。
(venv) [zaki@cloud-dev k8s]$ kubectl get ns testing NAME STATUS AGE testing Active 12s
再度ansible-playbook
を実行すると
TASK [Create a k8s namespace] ************************************************* ok: [localhost]
ちゃんとok
になる。
要は、以下のマニフェストをapply
したのと同じ状態。
apiVersion: v1 kind: Namespace metadata: name: testing
srcを使ってマニフェストファイル指定でdeployment
apiVersion: apps/v1 kind: Deployment metadata: labels: app: sample-http name: sample-http namespace: testing spec: replicas: 2 selector: matchLabels: app: sample-http template: metadata: labels: app: sample-http spec: containers: - image: httpd name: httpd
こんな内容のマニフェストファイルが既にある状態で、このファイルのパスを指定して以下のplaybookを作成。
(前述のnamespace作成のplaybookに追記)
- name: Create a Deployment by reading the definition from a local file community.kubernetes.k8s: state: present src: ./deployment.yaml
(venv) [zaki@cloud-dev k8s]$ ansible-playbook playbook.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [localhost] ************************************************************** TASK [Create a k8s namespace] ************************************************* ok: [localhost] TASK [Create a Deployment by reading the definition from a local file] ******** changed: [localhost] PLAY RECAP ******************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
これでDeploymentリソースが作られて(そこから更にReplicaSetが作られて)Podがデプロイされる。
(venv) [zaki@cloud-dev k8s]$ kubectl get pod -n testing NAME READY STATUS RESTARTS AGE sample-http-744f56bdc6-fgrdm 1/1 Running 0 9s sample-http-744f56bdc6-m6qv5 1/1 Running 0 9s
動いたね。
再実行すると、ちゃんとok
になります。
(追記)
上記の例はマニフェスト内にnamespace
を指定してるけど、マニフェストからはnamespace
指定を無くし、k8sモジュールのnamespace
パラメタをplaybookに記述しても動く。
apiVersion: apps/v1 kind: Deployment metadata: labels: app: sample-http name: sample-http spec: replicas: 2 # ...
playbook
community.kubernetes.k8s: state: present namespace: testing src: ./deployment.yaml
Jinja2テンプレートでマニフェスト生成
上記の例だと、例えばネームスペースは可変にしたいよなーとかあるかもしれません。
そういう既存マニフェストをそのままでなく、テンプレート的に使うには、AnsibleでおなじみJinja2が使えます。
k8sモジュールだとtemplate
を使用。
deployment.yaml.j2
というファイル名で、下記マニフェストファイルのj2テンプレートファイルを用意。
apiVersion: apps/v1 kind: Deployment metadata: labels: app: sample-http name: {{resourcename}} namespace: {{namespace}} spec: replicas: {{replica}} selector: matchLabels: app: sample-http template: metadata: labels: app: sample-http spec: containers: - image: httpd name: httpd
Ansible用マニフェスト。
- name: Read definition template file from the Ansible controller file system vars: resourcename: tmpl-sample namespace: testing replica: 1 community.kubernetes.k8s: state: present template: './deployment.yaml.j2'
このplaybookでansible-playbook
を実行すると、tmpl-sample
という名前でDeploymentが作成されるので、Podの状態も以下の通りになる。
(venv) [zaki@cloud-dev k8s]$ kubectl get pod -n testing NAME READY STATUS RESTARTS AGE sample-http-744f56bdc6-fgrdm 1/1 Running 0 23m sample-http-744f56bdc6-m6qv5 1/1 Running 0 23m tmpl-sample-744f56bdc6-7nklj 1/1 Running 0 5s
resource_definitionでインライン定義
- name: Create a Service object from an inline definition vars: namespace: testing community.kubernetes.k8s: state: present definition: apiVersion: v1 kind: Service metadata: labels: app: sample-http name: sample-http namespace: "{{ namespace }}" spec: ports: - port: 80 protocol: TCP targetPort: 80 name: http selector: app: sample-http type: NodePort
マニフェストのYAMLの内容をそのままPlaybookに書けばOK
Playbook内なので"{{ foobarbaz }}"
というお馴染みの形式で変数参照も可能。
これでansible-playbook
を実行すると
TASK [Create a Service object from an inline definition] ********************** changed: [localhost] PLAY RECAP ******************************************************************** localhost : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
この通り、Serviceリソースが作成できる。
(venv) [zaki@cloud-dev k8s]$ kubectl get svc -n testing NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE sample-http NodePort 10.110.123.20 <none> 80:30965/TCP 6s
認証用kubeconfigの指定
CI/CDとか。
とりあえず、~/.kube
とか$KUBECONFIG
とか設定されていないすっぴんOSユーザーを作って、そこで実行するための設定
[root@cloud-dev ~]# useradd -m sample-user [root@cloud-dev ~]# passwd sample-user
要はこの状態のsample-user
ユーザーで前述のPlaybookと同じものを実行。
TASK [Create a k8s namespace] ************************************************* fatal: [localhost]: FAILED! => changed=false msg: Failed to load kubeconfig due to Invalid kube-config file. No configuration found.
ただ実行するとこの通り。
Kubernetesのクラスター情報が何もないので。
kubeconfigファイル指定
例えばkindであれば、kind get kubeconfig --name <kindクラスター名> > kubeconfig.yml
を実行すれば、認証情報であるkubeconfigファイルを生成できる。
このファイルを指定すれば.kube/config
や環境変数$KUBECONFIG
が無くても動作する。
tasks: - name: Create a k8s namespace community.kubernetes.k8s: name: testing api_version: v1 kind: Namespace state: present kubeconfig: /home/sample-user/kubeconfig.yml
こんな感じ。
APIトークン指定
kubeconfig
指定でなく、ca_cert
やclient_cert
を個別に指定して動かせないか試したけど、ちょっとよくわからなかった。(実現可能かどうかも不明)
可能。
下記2つのパラメタを使用する。
api_key
: Secretのtoken
の値ca_cert
: Secretのca.crt
の内容が保存されたファイルのパス
必要な準備の大まかな流れは、処理用のアカウント(通常はServiceAccountがよいと思う)を作成し、このアカウントに対して適切な(Ansibleで操作する内容に必要な)RoleBindigを行い、そのSecretトークンを使用する。
ServiceAccount作成
マニフェストで作ってもよいし、kubectl
で作ってもよい。
$ kubectl create serviceaccount sample-account
Roleの作成
$ kubectl create role sample-role --verb=* --resource=*
※ これで作成するとapiGroups
が空文字1つを要素にもつRoleが作成されるが、この状態のRoleだとデプロイが成功しないため、kubectl edit
で"*"
に変更した。
rules: - apiGroups: - "" resources: - '*' verbs: - '*'
↑これを↓こうする
rules: - apiGroups: - '*' # <- ここ resources: - '*' verbs: - '*'
RoleBindig
作成したServiceAccountに作成したRoleを割り当てる。
$ kubectl create rolebinding sample-rolebinding --role=sample-role --serviceaccount=rbac-sample:sample-account
APIトークンの確認
Roleが設定されたServiceAccountのトークンを使用する。
トークンはSecretリソースに保持されている。
$ kubectl get secret # secretリソース名を確認 $ kubectl get secret (ServiceAccount名)-token-(ランダム文字列) -o jsonpath='{.data.token}' | base64 -d
証明書の確認
トークンと同じ要領で確認。
ただしAnsibleのk8sモジュールからはファイルのパスで指定する必要があるので、リダイレクトでファイル出力する。
$ kubectl get secret (ServiceAccount名)-token-(ランダム文字列) -o jsonpath='{.data.ca\.crt}' | base64 -d > data/ca_crt.crt
absentで削除
ここまでstate: present
で試していたけど、absent
を指定すれば削除動作になる。
- hosts: localhost gather_facts: no tasks: - name: delete namespace community.kubernetes.k8s: name: testing api_version: v1 kind: Namespace state: absent
このPlaybookでansible-playbook
を実行すれば
(venv) [zaki@cloud-dev k8s]$ kubectl get ns testing NAME STATUS AGE testing Terminating 129m
この通り削除実行が動き出す。