はじめに
この記事はNutanix Advent Calender 2021の12/3分の記事です。Nutanixを制御対象とし、Kubernetes Operatormに入門してみましたのでその成果を記します。
Kubernets Operatorとは?
Kubernetesの機能を拡張し、独自のリソース定義とコントローラを実装することでKubernetesによる運用自律化の仕組みをあらゆるアプリケーション・ミドルウェア・インフラに応用出来るようにするものです。
Operatorを実装するためのフレームワーク
今回はさくっとOperatorの仕組みを押さえたい、とりあえずシンプルなユースケースとしてNutanix AHVの仮想マシン制御する実装を行いたいので1番手っ取り早く実装できそうなkopfを選択しました。Githubのスター数を見ているとRedhat社が推しているOperator Frameworkが主流のようです。
必要なパーツ
- Kubernetesクラスタ: kubeadmを使用して1ノードクラスタを事前に作成しました。
- CRD(Custom Resource Definition): 独自APIリソースのスキーマをyaml形式で定義します
- カスタムコントローラ
- ソースコード: ControllerのロジックをPythonで実装します。
- Dockerfile: ソースコードとベースイメージを合わせてOperatorとして動作させるためのコンテナイメージを定義します
実装
1. CRDの準備
今回はカスタムリソースを利用してAHV VMを作成する所までやりたいので以下のahvvms.nutanix.comなるリソースを定義するCRDを準備しました。
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: ahvvms.nutanix.com
spec:
group: nutanix.com
scope: Namespaced
names:
kind: AhvVm
plural: ahvvms
singular: ahvvm
shortNames:
- avm
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
num_sockets:
minimum: 1
maximum: 4
type: integer
num_vcpus_per_socket:
minimum: 1
maximum: 2
type: integer
memory_size_mib:
minimum: 1024
maximum: 4096
type: integer
additionalPrinterColumns:
- name: Sockets
type: integer
jsonPath: .spec.num_sockets
description: Number of sockets
- name: vCPUsPerSocket
type: integer
jsonPath: .spec.num_vcpus_per_socket
description: Number of vCPUs per socket
- name: Memory
type: integer
jsonPath: .spec.memory_size_mib
description: Memory Size in MiB
これを準備したKubernetesクラスタに適用すると、CRDとして適用されます。
[sho.uchida@C02Y41RDJHD3 kopf-nutanix] (main)$ kubectl get crds
NAME CREATED AT
ahvvms.nutanix.com 2021-11-11T14:40:16Z
2. カスタムコントローラの準備
kopfにおけるコントローラの書き方に沿ってPythonでカスタムコントローラを実装します。接続先のPrism Centralの情報はコントローラPodの環境変数から参照する実装としました。中身はいたってシンプルで、ahvvms.nutanix.comのカスタムリソースが作成された際にNutanixのv3APIを使用して仮想マシンを作成します。
import kopf
import requests
import urllib3
import json
import os
import logging
import sys
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
logger = logging.getLogger(__name__)
sh = logging.StreamHandler(sys.stdout)
sh.setLevel(logging.DEBUG)
logger.addHandler(sh)
# Prism
PRISM_HOST = os.getenv('PRISM_HOST')
PRISM_USER = os.getenv('PRISM_USER')
PRISM_PASS = os.getenv('PRISM_PASS')
PRISM_PORT = os.getenv('PRISM_PORT', 9440)
PRISM_CLUSTERNAME = os.getenv('PRISM_CLUSTERNAME')
PRISM_CLUSTERUUID = os.getenv('PRISM_CLUSTERUUID')
@kopf.on.create('nutanix.com', 'v1alpha1', 'ahvvm')
def create_fn(spec, **kwargs):
vm_name = kwargs["body"]["metadata"]["name"]
num_sockets = spec.get('num_sockets', 1)
num_vcpus_per_socket = spec.get('num_vcpus_per_socket', 1)
memory_size_mib = spec.get('memory_size_mib', 1024)
url = f'https://{PRISM_HOST}:{PRISM_PORT}/api/nutanix/v3/vms'
payload = f'{{ \
"spec":{{ \
"name":"{vm_name}", \
"resources":{{ \
"num_sockets":{num_sockets}, \
"num_vcpus_per_socket":{num_vcpus_per_socket}, \
"memory_size_mib":{memory_size_mib} \
}}, \
"cluster_reference":{{ \
"kind":"cluster", \
"name":"{PRISM_CLUSTERNAME}", \
"uuid":"{PRISM_CLUSTERUUID}" \
}}\
}}, \
"metadata":{{ \
"kind":"vm" \
}} \
}}'
try:
response = session.post(url, data=payload)
if(response.ok):
logger.debug("OK")
else:
logger.error(f'An error occurred while connecting to {PRISM_HOST}.')
logger.error(response.text)
except Exception as ex:
logger.error(f'An {type(ex).__name__} exception occurred while connecting to {PRISM_HOST}.\nArgument: {ex.args}.')
session = requests.Session()
session.auth = (PRISM_USER, PRISM_PASS)
session.verify = False
session.headers.update({"Content-Type": "application/json", "Accept": "application/json", "cache-control": "no-cache"})
logger.debug("Session is created.")
このソースコードを使用し、pythonのベースイメージを使ってコンテナイメージをビルドします。
FROM python:3.7
RUN mkdir /src
ADD 2_cc.py /src
ADD requirements.txt .
RUN pip3 install -r requirements.txt
CMD kopf run /src/2_cc.py --verbose
[sho.uchida@C02Y41RDJHD3 kopf-nutanix] (main)$ docker build -t (Dockerレポジトリ名)/kopf-nutanix:latest .
[sho.uchida@C02Y41RDJHD3 kopf-nutanix] (main)$ docker push (Dockerレポジトリ名)/kopf-nutanix:latest
3. カスタムコントローラの展開
2でビルド・プッシュしたコントローラのコンテナイメージを元に、コントローラをKubernetesクラスタ上に展開します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: ahvvm-operator
namespace: kopf-system
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
application: ahvvm-operator
template:
metadata:
labels:
application: ahvvm-operator
spec:
serviceAccountName: ahvvm-operator
containers:
- name: ahvvms
image: (Dockerレポジトリ名)/kopf-nutanix:latest
imagePullPolicy: Always
env:
- name: PRISM_HOST
value: "Prism Centralのホスト名"
- name: PRISM_USER
value: "Prism Centralのユーザ名"
- name: PRISM_PASS
value: "Prism Centralのパスワード"
- name: PRISM_PORT
value: "Prism Centralのポート番号、たぶん9440"
- name: PRISM_CLUSTERNAME
value: "クラスタ名"
- name: PRISM_CLUSTERUUID
value: "クラスタUUID"
カスタムコントローラが展開出来ていることを確認します。
[sho.uchida@C02Y41RDJHD3 kopf-nutanix] (main)$ kubectl get all -n kopf-system
NAME READY STATUS RESTARTS AGE
pod/ahvvm-operator-5d78c4b94f-cj9p9 1/1 Running 0 20d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ahvvm-operator 1/1 1 1 20d
NAME DESIRED CURRENT READY AGE
replicaset.apps/ahvvm-operator-5d78c4b94f 1 1 1 20d
4. カスタムリソースの適用
では、CRDとカスタムコントローラが展開されましたので、CRDで定義されたモデルに則ったカスタムリソースを作成します。
apiVersion: nutanix.com/v1alpha1
kind: AhvVm
metadata:
name: ahvvm-from-operator
spec:
num_sockets: 2
num_vcpus_per_socket: 1
memory_size_mib: 2048
ahvvmのリソースを確認すると、新たに作成したahvvm-from-operatorのオブジェクトが出来ています。
[sho.uchida@C02Y41RDJHD3 kopf-nutanix] (main)$ kubectl get ahvvm
NAME SOCKETS VCPUSPERSOCKET MEMORY
ahvvm-from-operator 2 1 2048
コントローラのログ確認。
[sho.uchida@C02Y41RDJHD3 kopf-nutanix] (main)$ kubectl logs ahvvm-operator-5d78c4b94f-cj9p9 -n kopf-system
~~~略~~~
[2021-11-12 00:06:13,086] kopf.objects [DEBUG ] [default/ahvvm-from-operator] Creation is in progress: {'apiVersion': 'nutanix.com/v1alpha1', 'kind': 'AhvVm', 'metadata': {'annotations': {'kubectl.kubernetes.io/last-applied-configuration': '{"apiVersion":"nutanix.com/v1alpha1","kind":"AhvVm","metadata":{"annotations":{},"name":"ahvvm-from-operator","namespace":"default"},"spec":{"memory_size_mib":2048,"num_sockets":2,"num_vcpus_per_socket":1}}\n'}, 'creationTimestamp': '2021-11-12T00:06:12Z', 'generation': 1, 'managedFields': [{'apiVersion': 'nutanix.com/v1alpha1', 'fieldsType': 'FieldsV1', 'fieldsV1': {'f:metadata': {'f:annotations': {'.': {}, 'f:kubectl.kubernetes.io/last-applied-configuration': {}}}, 'f:spec': {'.': {}, 'f:memory_size_mib': {}, 'f:num_sockets': {}, 'f:num_vcpus_per_socket': {}}}, 'manager': 'kubectl', 'operation': 'Update', 'time': '2021-11-12T00:06:12Z'}], 'name': 'ahvvm-from-operator', 'namespace': 'default', 'resourceVersion': '636082', 'uid': '92cf13c0-c68f-4ef8-8d18-ade4aac450df'}, 'spec': {'memory_size_mib': 2048, 'num_sockets': 2, 'num_vcpus_per_socket': 1}}
[2021-11-12 00:06:13,087] kopf.objects [DEBUG ] [default/ahvvm-from-operator] Handler 'create_fn' is invoked.
[2021-11-12 00:06:13,090] urllib3.connectionpo [DEBUG ] Resetting dropped connection: 10.42.9.39
[2021-11-12 00:06:13,806] urllib3.connectionpo [DEBUG ] https://10.42.9.39:9440 "POST /api/nutanix/v3/vms HTTP/1.1" 202 None
[2021-11-12 00:06:13,807] __kopf_script_0__/sr [DEBUG ] OK
OK
[2021-11-12 00:06:13,808] kopf.objects [INFO ] [default/ahvvm-from-operator] Handler 'create_fn' succeeded.
[2021-11-12 00:06:13,808] kopf.objects [INFO ] [default/ahvvm-from-operator] Creation is processed: 1 succeeded; 0 failed.
[2021-11-12 00:06:13,809] kopf.objects [DEBUG ] [default/ahvvm-from-operator] Patching with: {'metadata': {'annotations': {'kopf.zalando.org/last-handled-configuration': '{"spec":{"memory_size_mib":2048,"num_sockets":2,"num_vcpus_per_socket":1}}\n'}}}
[2021-11-12 00:06:13,939] kopf.objects [DEBUG ] [default/ahvvm-from-operator] Something has changed, but we are not interested (the essence is the same).
[2021-11-12 00:06:13,939] kopf.objects [DEBUG ] [default/ahvvm-from-operator] Handling cycle is finished, waiting for new changes.
そして肝心のAHV VMが作成されているかを確認してみると、めでたく指定されたスペックで仮想マシンが作成されています。
終わりに
今回はVM作成という非常にシンプルなユースケースで手っ取り早く実装を行いましたのでOperatorがただのAPI変換マシーンになっています。今後、VMレプリカ数を制御し、障害時は新たな仮想マシンを構築する等、KubernetesのControllerが内部実行するReconciliation Loopの仕組みを利用したNutanixの運用自律化の構築に期待が出来る内容となっています。また、Operator Frameworkでの実装にもチャレンジしたいと思っています。以上!