Kubernetes「で」構成管理

2019年12月18日 水曜日


【この記事を書いた人】
李 瀚

締め切りドリブンな生活から脱却したい

「Kubernetes「で」構成管理」のイメージ
IIJ Engineers blog読者プレゼントキャンペーン

Twitterフォロー&条件付きツイートで「バリーくんぬいぐるみ」を抽選で20名にプレゼント!
応募期間は2019/11/29~2019/12/31まで。詳細はこちらをご覧ください。
今すぐツイートするならこちら→ フォローもお忘れなく!

IIJ 2019 TECHアドベントカレンダー 12/18(水)の記事です】

Kubernetesは、アプリーケーションの開発・運用においてスケーラビリティと耐障害性を確保するのにとても便利な基盤です。しかしながら、このような用途でKubernetesを使うにはクラウドネイティブなアプリケーションが必要となったり、特にインフラの運用においては必ずしもout-of-the-boxなKubernetesがユースケースにはまらないことのほうが多いのではないでしょうか。

この記事では、KubernetesのCustom Resource Definition(CRD)とOperatorを使うことによってマルチテナントのvSphere環境から情報を取得し、あまり手間をかけずにオンプレミスなインフラ環境のアクセシビリティを改善することで、運用コストを低減できるかもしれない方法を紹介します。

インフラ寄り過ぎて、クラウドネイティブな流行りを傍から眺めている方でも、明日からKubernetesネイティブな生活をはじめられる…かもしれません。

オンプレミスなインフラ基盤の課題

具体的な実装の話に入る前に、まず解決したいインフラ基盤の運用における課題について少しまとめます。

構成管理について

インフラ基盤の運用において、構成管理は常に悩みの種ではないでしょうか。

  • メンテナンスの対象を減らすためにデータベースの利用を避ける
  • 検証環境時にテキストファイルから始めた管理が本番環境でも維持される
  • UI/CLIを作りこむ暇がない
  • Excel台帳

などなど、様々な理由によってオンプレミスなインフラ基盤では人によって構成管理がなされることが珍しくありません。このような構成管理では、構成管理のドキュメントは独立して管理されるため、運用に活用することも難しくなります。そして、構成管理はそれだけで独立しているため、リソースを投入してよりよい管理方法にするためのモチベーションも高くなくなってしまいます。

アクセシビリティについて

構成管理の作りこみが難しいとなると、構成の一覧性も低くなってしまいます。普段操作しているものとは異なる表示やフィルタをかけるためのUIが作られることもまれになります。作業者は管理されている情報をほかのツールや自分の脳内で変換することによって作業することも求められます。本来このような機能は構成管理に集約することで利便性を高めることができます。

概略:CRDとOperatorを組み合わせてKubernetesからvCenterの情報を取得する

上述の課題を解決するための選択肢として、Kubernetesを試しに使ってみることにします。

Kubernetesを採用する理由

  1. 構成管理ツールとしての実績があります。
    • Kubernetesは、etcdとそれとうまく連携するAPI群、として捉えることができ、その本体はただのデータベースです。
  2. コマンドラインで管理が完結します。
    • プライベートクラウドにおいて、コマンドラインで操作できれば、テナントユーザからはパブリッククラウドと同等の使い勝手になるはずです。構成管理の情報をKubernetesに入れることによって、自然とコマンドラインツールが利用できます。
  3. Kubernetesはそもそも運用を補助するために作られています。
    • Kubernetesでは構成情報を格納するだけでなく、格納された情報を利用することで自動化まで行うことができます。

やること

  • vCenterのテナントユーザの認証情報をKubernetesに入れることで仮想マシンの一覧が取得できるようにします。

完成図

ちょっとこれだけでは、何をやりたいのかがわかりにくいと思うので、kubectlで情報を一覧化できる完成図を先にお見せします。

以下のようなテナントユーザの情報を登録することで、このテナントが管理している仮想マシンの情報を取得できるようにします。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# kubectl get tenant
NAME USERNAME TARGETNAMESPACE EXECDATE EXECFLAG AGE
test ike-admin@vsphere.local test-tenant 4s false 4s
# kubectl get tenant NAME USERNAME TARGETNAMESPACE EXECDATE EXECFLAG AGE test ike-admin@vsphere.local test-tenant 4s false 4s
# kubectl get tenant
NAME   USERNAME                  TARGETNAMESPACE   EXECDATE   EXECFLAG   AGE
test   ike-admin@vsphere.local   test-tenant       4s         false      4s

以下のように仮想マシンの情報をユーザが手入力しなくても取得できるOperatorを作成します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# kubectl get vm -n test-tenant
NAME POWERSTATE NUMCPU MEMORYMB IPS UPDATEDATE AGE
nwcd-ike-a-m1 poweredOn 2 2048 [10.141.86.60] 47s 47s
nwcd-ike-a-w1 poweredOn 2 2048 [10.141.86.70] 47s 47s
nwcd-ike-admin1500-stg poweredOn 2 8192 [10.141.86.11] 47s 46s
nwcd-ike-centos poweredOn 2 8192 [10.141.86.23] 47s 46s
nwcd-ike-clean poweredOff 2 8192 [] 47s 46s
nwcd-ike-gate1500-stg poweredOn 1 1024 [10.141.86.10] 47s 47s
nwcd-ike-router1500-stg poweredOn 1 512 [10.141.86.200] 47s 46s
nwcd-ike-template1500-stg poweredOff 2 8192 [] 47s 46s
nwcd-ike-ubuntu-base poweredOff 2 2048 [] 47s 46s
nwcd-ike-ubuntu-template poweredOn 2 2048 [10.141.86.20] 47s 46s
nwcd-ike-ubuntu-template-stg poweredOff 2 2048 [] 47s 46s
# kubectl get vm -n test-tenant NAME POWERSTATE NUMCPU MEMORYMB IPS UPDATEDATE AGE nwcd-ike-a-m1 poweredOn 2 2048 [10.141.86.60] 47s 47s nwcd-ike-a-w1 poweredOn 2 2048 [10.141.86.70] 47s 47s nwcd-ike-admin1500-stg poweredOn 2 8192 [10.141.86.11] 47s 46s nwcd-ike-centos poweredOn 2 8192 [10.141.86.23] 47s 46s nwcd-ike-clean poweredOff 2 8192 [] 47s 46s nwcd-ike-gate1500-stg poweredOn 1 1024 [10.141.86.10] 47s 47s nwcd-ike-router1500-stg poweredOn 1 512 [10.141.86.200] 47s 46s nwcd-ike-template1500-stg poweredOff 2 8192 [] 47s 46s nwcd-ike-ubuntu-base poweredOff 2 2048 [] 47s 46s nwcd-ike-ubuntu-template poweredOn 2 2048 [10.141.86.20] 47s 46s nwcd-ike-ubuntu-template-stg poweredOff 2 2048 [] 47s 46s
# kubectl get vm -n test-tenant
NAME                                        POWERSTATE   NUMCPU   MEMORYMB   IPS               UPDATEDATE   AGE
nwcd-ike-a-m1                               poweredOn    2        2048       [10.141.86.60]    47s          47s
nwcd-ike-a-w1                               poweredOn    2        2048       [10.141.86.70]    47s          47s
nwcd-ike-admin1500-stg                      poweredOn    2        8192       [10.141.86.11]    47s          46s
nwcd-ike-centos                             poweredOn    2        8192       [10.141.86.23]    47s          46s
nwcd-ike-clean                              poweredOff   2        8192       []                47s          46s
nwcd-ike-gate1500-stg                       poweredOn    1        1024       [10.141.86.10]    47s          47s
nwcd-ike-router1500-stg                     poweredOn    1        512        [10.141.86.200]   47s          46s
nwcd-ike-template1500-stg                   poweredOff   2        8192       []                47s          46s
nwcd-ike-ubuntu-base                        poweredOff   2        2048       []                47s          46s
nwcd-ike-ubuntu-template                    poweredOn    2        2048       [10.141.86.20]    47s          46s
nwcd-ike-ubuntu-template-stg                poweredOff   2        2048       []                47s          46s

Kubernetes流行のきざし

VMwareが今後vSphereにKubernetesを取り込んで展開していくことが今年話題になりました。オンプレミス環境でもKubernetesがはやり始めるのは時間の問題でしょう。

しかしながらVMwareからKubernetesが提供されるまでにはまだ少し時間がかかりそうです。vCenter 6.7U3が必要だったりNSX-Tが必要だったり、そもそもリリースされていないので試せない現状もありますが、リリースされてから基盤に採用されるまでにもラグがありそうです。

そこで、とりあえずミニマムで簡単にvCenterの情報をkubectlコマンドを用いて見られるようにしてみたいと思います。

参考

Operator作成に当たって、主に以下の記事・動画とライブラリのドキュメントを参考にしました。

使うもの

  • vCenter 6.5
  • Kubernetes 1.16
  • Python 3.7
  • ライブラリ
    • kopf==0.23.2
    • kubernetes==10.0.1
    • pyvmomi==6.7.3

CRDの作成

最初にKubernetesに登録するデータの構造を定義します。一般的なYAML形式での構成管理と同じですが、Kubernetesを用いることで、バリデーションや更新差分を簡単に見られるようになります。

次に、マルチテナントユーザの環境を仮定して、以下の情報を格納するためのCRDの定義を行います。

  • テナントに関する情報
  • 仮想マシンに関する情報

注釈

Custom Resource Definition(CRD)(https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/)はKubernetesに新しいリソースの定義を追加するものです。KubernetesにはPod/Deployment/Serviceなどのリソースタイプがありますが、これら以外に好きなリソースタイプを追加する方法がCRDです。CRDによって定義されたリソースタイプについてはget/set/describe/editなどができ、Operator(https://kubernetes.io/docs/concepts/extend-kubernetes/operator/)を作成することで、リソース作成・変更・削除などの際の振る舞いを制御することができます。

テナントCRD

まずはテナントユーザの認証情報を受け入れる箱を作ります。ここで必要な情報はユーザ名とパスワードだけです。パスワードを平文で扱うことを回避するためにSecret経由で与えるようにします。additionalPrinterColumnsを書いておくことで、getする際にきれいに表示できるようになります。(表示例は後述)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tenants.test.local
spec:
group: test.local
versions:
- name: v1
served: true
storage: true
scope: Namespaced
names:
plural: tenants
singular: tenant
kind: Tenant
shortNames:
- tenant
additionalPrinterColumns:
- name: Username
type: string
JSONPath: .username
description: Username for vCenter tenant account
- name: TargetNamespace
type: string
JSONPath: .namespace
- name: ExecDate
type: date
JSONPath: .execDate
- name: ExecFlag
type: boolean
JSONPath: .execFlag
- name: Age
type: date
JSONPath: .metadata.creationTimestamp
validation:
openAPIV3Schema:
type: object
properties:
username:
type: string
passwordName:
type: string
namespace:
type: string
execFlag:
type: boolean
execDate:
type: string
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: tenants.test.local spec: group: test.local versions: - name: v1 served: true storage: true scope: Namespaced names: plural: tenants singular: tenant kind: Tenant shortNames: - tenant additionalPrinterColumns: - name: Username type: string JSONPath: .username description: Username for vCenter tenant account - name: TargetNamespace type: string JSONPath: .namespace - name: ExecDate type: date JSONPath: .execDate - name: ExecFlag type: boolean JSONPath: .execFlag - name: Age type: date JSONPath: .metadata.creationTimestamp validation: openAPIV3Schema: type: object properties: username: type: string passwordName: type: string namespace: type: string execFlag: type: boolean execDate: type: string
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tenants.test.local
spec:
  group: test.local
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: tenants
    singular: tenant
    kind: Tenant
    shortNames:
    - tenant
  additionalPrinterColumns:
    - name: Username
      type: string
      JSONPath: .username
      description: Username for vCenter tenant account
    - name: TargetNamespace
      type: string
      JSONPath: .namespace
    - name: ExecDate
      type: date
      JSONPath: .execDate
    - name: ExecFlag
      type: boolean
      JSONPath: .execFlag
    - name: Age
      type: date
      JSONPath: .metadata.creationTimestamp
  validation:
    openAPIV3Schema:
      type: object
      properties:
        username:
          type: string
        passwordName:
          type: string
        namespace:
          type: string
        execFlag:
          type: boolean
        execDate:
          type: string

仮想マシンCRD

次に、VM情報のCRDを作ります。ここではPowerCLIでGet-VMしたときによく表示される電源状態、CPU数、メモリ量のほかに運用でよく知りたい情報であるIPアドレスを格納できるようにします。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: vms.test.local
spec:
group: test.local
versions:
- name: v1
served: true
storage: true
scope: Namespaced
names:
plural: vms
singular: vm
kind: VM
shortNames:
- vm
additionalPrinterColumns:
- name: PowerState
type: string
JSONPath: .powerState
- name: NumCpu
type: number
JSONPath: .numCpu
- name: MemoryMB
type: number
JSONPath: .memoryMB
- name: IPs
type: string
JSONPath: .ip
- name: UpdateDate
type: date
JSONPath: .updateDate
- name: Age
type: date
JSONPath: .metadata.creationTimestamp
validation:
openAPIV3Schema:
type: object
properties:
vmname:
type: string
powerState:
type: string
numCpu:
type: number
memoryMB:
type: number
ip:
type: array
items:
type: string
updateDate:
type: string
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: vms.test.local spec: group: test.local versions: - name: v1 served: true storage: true scope: Namespaced names: plural: vms singular: vm kind: VM shortNames: - vm additionalPrinterColumns: - name: PowerState type: string JSONPath: .powerState - name: NumCpu type: number JSONPath: .numCpu - name: MemoryMB type: number JSONPath: .memoryMB - name: IPs type: string JSONPath: .ip - name: UpdateDate type: date JSONPath: .updateDate - name: Age type: date JSONPath: .metadata.creationTimestamp validation: openAPIV3Schema: type: object properties: vmname: type: string powerState: type: string numCpu: type: number memoryMB: type: number ip: type: array items: type: string updateDate: type: string
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: vms.test.local
spec:
  group: test.local
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: vms
    singular: vm
    kind: VM
    shortNames:
    - vm
  additionalPrinterColumns:
    - name: PowerState
      type: string
      JSONPath: .powerState
    - name: NumCpu
      type: number
      JSONPath: .numCpu
    - name: MemoryMB
      type: number
      JSONPath: .memoryMB
    - name: IPs
      type: string
      JSONPath: .ip
    - name: UpdateDate
      type: date
      JSONPath: .updateDate
    - name: Age
      type: date
      JSONPath: .metadata.creationTimestamp
  validation:
    openAPIV3Schema:
      type: object
      properties:
        vmname:
          type: string
        powerState:
          type: string
        numCpu:
          type: number
        memoryMB:
          type: number
        ip:
          type: array
          items:
            type: string
        updateDate:
          type: string

Operatorの作成

次に入力されたテナントの情報から、そのテナントが管理している仮想マシンの情報を取得するためのOperatorを作成します。

新規の基盤で既存の環境がなければ、構成は宣言的に管理されるべきですが、既存の基盤上ですでに仮想マシンが動いているときに、テナントにその情報をすべて入力させるのは酷です。幸いにして、この取得は難しくありません。

OperatorはGo言語で書かれることが一般的だと思いますが、性能の要求が高くないので、Pythonのフレームワークを使って簡単に作成してしまいます。

全体の流れ

  • CRDが作成・更新されるのをフックとします。
  • 実行防止フラグが立っていなければ以下を実行します。
    • Secretからパスワードを取得します。
    • pyvmomiを使ってvCenterに接続して仮想マシンの情報を取得します。
    • 取得した情報をKubernetesのDBへ書き込みます。
    • 実行防止フラグを立てます。

Dockerfile

Operatorはコンテナで作成してKubernetesクラスタ内にデプロイすることで利用できるようになります。Pythonの公式コンテナイメージに必要なライブラリをインストールして、実行するソースコードを添えるだけです。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
FROM python:3.7
RUN pip install kopf
RUN pip install kubernetes
RUN pip install pyvmomi
COPY handlers.py /handlers.py
CMD kopf run --standalone /handlers.py
FROM python:3.7 RUN pip install kopf RUN pip install kubernetes RUN pip install pyvmomi COPY handlers.py /handlers.py CMD kopf run --standalone /handlers.py
FROM python:3.7
 
RUN pip install kopf
RUN pip install kubernetes
RUN pip install pyvmomi
 
COPY handlers.py /handlers.py
 
CMD kopf run --standalone /handlers.py

イベント応答

kopf(https://github.com/zalando-incubator/kopf)はPythonでKubernetesのOperatorを作るためのライブラリです。今回は作成時と更新時に同じ挙動をさせるためにon.createとon.updateに同じ関数を呼び出すようにして、common_fn内に処理を実装します。実装方法はexamples(https://github.com/zalando-incubator/kopf/tree/master/examples)を参照しました。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@kopf.on.create(CRD_GROUP, CRD_VERSION, CRD_PLURAL)
def create_fn(body, **kwargs):
common_fn(body, **kwargs)
@kopf.on.update(CRD_GROUP, CRD_VERSION, CRD_PLURAL)
def update_fn(body, **kwargs):
common_fn(body, **kwargs)
@kopf.on.create(CRD_GROUP, CRD_VERSION, CRD_PLURAL) def create_fn(body, **kwargs): common_fn(body, **kwargs) @kopf.on.update(CRD_GROUP, CRD_VERSION, CRD_PLURAL) def update_fn(body, **kwargs): common_fn(body, **kwargs)
@kopf.on.create(CRD_GROUP, CRD_VERSION, CRD_PLURAL)
def create_fn(body, **kwargs):
    common_fn(body, **kwargs)
 
 
@kopf.on.update(CRD_GROUP, CRD_VERSION, CRD_PLURAL)
def update_fn(body, **kwargs):
    common_fn(body, **kwargs)

Kubernetesクラスタとの連携

kubernetes-clientはPythonでKubernetesのAPIを簡単に実行するためのライブラリです。APIを呼ぶためのドキュメントが充実していて、大変使いやすいです。(https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md

このライブラリを用いて、vCenterから取得した情報を仮想マシンのCRDに変換してKubernetesクラスタに投入します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
kapi = kubernetes.client.CoreV1Api()
capi = kubernetes.client.CustomObjectsApi()
kapi = kubernetes.client.CoreV1Api() capi = kubernetes.client.CustomObjectsApi()
kapi = kubernetes.client.CoreV1Api()
capi = kubernetes.client.CustomObjectsApi()

参考:https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Secret.md

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
res = kapi.read_namespaced_secret(password_name, namespace, exact=True, export=True)
res = kapi.read_namespaced_secret(password_name, namespace, exact=True, export=True)
res = kapi.read_namespaced_secret(password_name, namespace, exact=True, export=True)
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
info = {
'vmname': vmname,
'powerState': power_state,
'numCpu': num_cpu,
'memoryMB': memory_mb,
'ip': ip,
}
kopf.info(body, reason='vm-info', message=json.dumps(info))
rec = {
'apiVersion': f"{CRD_GROUP}/{CRD_VERSION}",
'kind': C_CRD_KIND,
'metadata': {
"name": vmname,
"namespace": target_namespace,
},
'updateDate': access_date,
}
rec.update(info)
obj = {
'group': CRD_GROUP,
'version': CRD_VERSION,
'plural': C_CRD_PLURAL,
'namespace': target_namespace,
'body': rec,
}
try:
capi.create_namespaced_custom_object(**obj)
except Exception as e:
kopf.info(body, reason='vm-info-creation-error', message=str(e))
obj['name'] = obj['body']['metadata']['name']
capi.patch_namespaced_custom_object(**obj)
info = { 'vmname': vmname, 'powerState': power_state, 'numCpu': num_cpu, 'memoryMB': memory_mb, 'ip': ip, } kopf.info(body, reason='vm-info', message=json.dumps(info)) rec = { 'apiVersion': f"{CRD_GROUP}/{CRD_VERSION}", 'kind': C_CRD_KIND, 'metadata': { "name": vmname, "namespace": target_namespace, }, 'updateDate': access_date, } rec.update(info) obj = { 'group': CRD_GROUP, 'version': CRD_VERSION, 'plural': C_CRD_PLURAL, 'namespace': target_namespace, 'body': rec, } try: capi.create_namespaced_custom_object(**obj) except Exception as e: kopf.info(body, reason='vm-info-creation-error', message=str(e)) obj['name'] = obj['body']['metadata']['name'] capi.patch_namespaced_custom_object(**obj)
info = {
        'vmname': vmname,
        'powerState': power_state,
        'numCpu': num_cpu,
        'memoryMB': memory_mb,
        'ip': ip,
       }
kopf.info(body, reason='vm-info', message=json.dumps(info))
rec = {
       'apiVersion': f"{CRD_GROUP}/{CRD_VERSION}",
       'kind': C_CRD_KIND,
       'metadata': {
                    "name": vmname,
                    "namespace": target_namespace,
                   },
       'updateDate': access_date,
      }
rec.update(info)
obj = {
       'group': CRD_GROUP,
       'version': CRD_VERSION,
       'plural': C_CRD_PLURAL,
       'namespace': target_namespace,
       'body': rec,
      }
 
try:
    capi.create_namespaced_custom_object(**obj)
except Exception as e:
    kopf.info(body, reason='vm-info-creation-error', message=str(e))
    obj['name'] = obj['body']['metadata']['name']
    capi.patch_namespaced_custom_object(**obj)
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ubody = body
ubody['execFlag'] = False
ubody['execDate'] = access_date
meta = {
'labels': ubody['metadata']['labels'],
}
ubody['metadata'] = meta
obj = {
'group': CRD_GROUP,
'version': CRD_VERSION,
'plural': CRD_PLURAL,
'name': name,
'namespace': namespace,
'body': ubody,
}
kopf.info(body, reason='req-payload', message=json.dumps(obj))
capi.patch_namespaced_custom_object(**obj)
ubody = body ubody['execFlag'] = False ubody['execDate'] = access_date meta = { 'labels': ubody['metadata']['labels'], } ubody['metadata'] = meta obj = { 'group': CRD_GROUP, 'version': CRD_VERSION, 'plural': CRD_PLURAL, 'name': name, 'namespace': namespace, 'body': ubody, } kopf.info(body, reason='req-payload', message=json.dumps(obj)) capi.patch_namespaced_custom_object(**obj)
ubody = body
ubody['execFlag'] = False
ubody['execDate'] = access_date
meta = {
        'labels': ubody['metadata']['labels'],
       }
ubody['metadata'] = meta
obj = {
       'group': CRD_GROUP,
       'version': CRD_VERSION,
       'plural': CRD_PLURAL,
       'name': name,
       'namespace': namespace,
       'body': ubody,
      }
kopf.info(body, reason='req-payload', message=json.dumps(obj))
capi.patch_namespaced_custom_object(**obj)

vCenterとの接続

pyvmomi(https://github.com/vmware/pyvmomi)はPythonでvCenterとやり取りをするためのライブラリです。ユーザが入力したテナントの情報を用いてvCenterにログインし、仮想マシンなどの情報を取得します。

参考:https://github.com/vmware/pyvmomi/blob/master/sample/getallvms.py

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#--- vCenter connection
si = SmartConnect(host=getenv('VCENTER_HOST'),
user=username,
pwd=password,
)
if not si:
kopf.warn(body, reason='auth', message="connection error")
msg = f"VM info created by Tenant"
return {'message': msg}
atexit.register(Disconnect, si)
#=== vCenter connection
#--- vCenter connection si = SmartConnect(host=getenv('VCENTER_HOST'), user=username, pwd=password, ) if not si: kopf.warn(body, reason='auth', message="connection error") msg = f"VM info created by Tenant" return {'message': msg} atexit.register(Disconnect, si) #=== vCenter connection
#--- vCenter connection
        si = SmartConnect(host=getenv('VCENTER_HOST'),
                          user=username,
                          pwd=password,
                         )
 
        if not si:
            kopf.warn(body, reason='auth', message="connection error")
            msg = f"VM info created by Tenant"
            return {'message': msg}
 
        atexit.register(Disconnect, si)
#=== vCenter connection

参考:https://github.com/vmware/pyvmomi-community-samples/blob/master/samples/vminfo_quick.py

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
dc = si.content.rootFolder.childEntity[0]
folders = dc.vmFolder.childEntity
for folder in folders:
vms = folder.childEntity
for vm in vms:
summary = vm.summary
vmname = vm.config.name
template = vm.config.template
ip = vm.guest.ipAddress
if ip is None:
ip = []
elif type(ip) is not list:
ip = [ip]
power_state = vm.runtime.powerState
num_cpu = vm.config.hardware.numCPU
memory_mb = vm.config.hardware.memoryMB
dc = si.content.rootFolder.childEntity[0] folders = dc.vmFolder.childEntity for folder in folders: vms = folder.childEntity for vm in vms: summary = vm.summary vmname = vm.config.name template = vm.config.template ip = vm.guest.ipAddress if ip is None: ip = [] elif type(ip) is not list: ip = [ip] power_state = vm.runtime.powerState num_cpu = vm.config.hardware.numCPU memory_mb = vm.config.hardware.memoryMB
dc = si.content.rootFolder.childEntity[0]
folders = dc.vmFolder.childEntity
 
for folder in folders:
    vms = folder.childEntity
    for vm in vms:
        summary = vm.summary
        vmname = vm.config.name
        template = vm.config.template
        ip = vm.guest.ipAddress
        if ip is None:
            ip = []
        elif type(ip) is not list:
            ip = [ip]
        power_state = vm.runtime.powerState
        num_cpu = vm.config.hardware.numCPU
        memory_mb = vm.config.hardware.memoryMB

その他

いたるところにある kopf.info(body, reason='name', message=name) はprintデバッグをするための情報表示…でもありますが、これをしておくことによってdescribeするとログに残るようにしています。(ログはKubernetesクラスタの設定次第ですが、デフォルトだと1時間だけ保持されます)

ソースコード全文

以下、ソースコードをまとめた全文です。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import base64
import yaml
import json
import datetime
import atexit
from os import getenv
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
import kopf
import kubernetes
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim
CRD_GROUP = 'test.local'
CRD_VERSION = 'v1'
CRD_PLURAL = 'tenants'
C_CRD_PLURAL = 'vms'
C_CRD_KIND = 'VM'
def decode_secret(s):
return base64.b64decode(s).decode()
@kopf.on.create(CRD_GROUP, CRD_VERSION, CRD_PLURAL)
def create_fn(body, **kwargs):
common_fn(body, **kwargs)
@kopf.on.update(CRD_GROUP, CRD_VERSION, CRD_PLURAL)
def update_fn(body, **kwargs):
common_fn(body, **kwargs)
def common_fn(body, **kwargs):
kapi = kubernetes.client.CoreV1Api()
capi = kubernetes.client.CustomObjectsApi()
exec_flag = body.get('execFlag', True)
if exec_flag is not False:
name = body['metadata']['name']
kopf.info(body, reason='name', message=name)
namespace = body['metadata']['namespace']
kopf.info(body, reason='namespace', message=namespace)
target_namespace = body['namespace']
kopf.info(body, reason='target_namespace', message=target_namespace)
username = body['username']
kopf.info(body, reason='username', message=username)
password_name = body['passwordName']
res = kapi.read_namespaced_secret(password_name, namespace, exact=True, export=True)
password = decode_secret(res.data['password'])
#--- vCenter connection
si = SmartConnect(host=getenv('VCENTER_HOST'),
user=username,
pwd=password,
)
if not si:
kopf.warn(body, reason='auth', message="connection error")
msg = f"VM info created by Tenant"
return {'message': msg}
atexit.register(Disconnect, si)
#=== vCenter connection
access_date = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
kopf.info(body, reason='access-date', message=access_date)
dc = si.content.rootFolder.childEntity[0]
folders = dc.vmFolder.childEntity
kopf.info(body, reason='dc', message=dc.name)
for folder in folders:
kopf.info(body, reason='folder', message=folder.name)
vms = folder.childEntity
for vm in vms:
kopf.info(body, reason='vm', message=vm.name)
summary = vm.summary
vmname = vm.config.name
template = vm.config.template
ip = vm.guest.ipAddress
if ip is None:
ip = []
elif type(ip) is not list:
ip = [ip]
power_state = vm.runtime.powerState
num_cpu = vm.config.hardware.numCPU
memory_mb = vm.config.hardware.memoryMB
info = {
'vmname': vmname,
'powerState': power_state,
'numCpu': num_cpu,
'memoryMB': memory_mb,
'ip': ip,
}
kopf.info(body, reason='vm-info', message=json.dumps(info))
rec = {
'apiVersion': f"{CRD_GROUP}/{CRD_VERSION}",
'kind': C_CRD_KIND,
'metadata': {
"name": vmname,
"namespace": target_namespace,
},
'updateDate': access_date,
}
rec.update(info)
obj = {
'group': CRD_GROUP,
'version': CRD_VERSION,
'plural': C_CRD_PLURAL,
'namespace': target_namespace,
'body': rec,
}
try:
capi.create_namespaced_custom_object(**obj)
except Exception as e:
kopf.info(body, reason='vm-info-creation-error', message=str(e))
obj['name'] = obj['body']['metadata']['name']
capi.patch_namespaced_custom_object(**obj)
ubody = body
ubody['execFlag'] = False
ubody['execDate'] = access_date
meta = {
'labels': ubody['metadata']['labels'],
}
ubody['metadata'] = meta
obj = {
'group': CRD_GROUP,
'version': CRD_VERSION,
'plural': CRD_PLURAL,
'name': name,
'namespace': namespace,
'body': ubody,
}
kopf.info(body, reason='req-payload', message=json.dumps(obj))
capi.patch_namespaced_custom_object(**obj)
import base64 import yaml import json import datetime import atexit from os import getenv import ssl ssl._create_default_https_context = ssl._create_unverified_context import kopf import kubernetes from pyVim.connect import SmartConnect, Disconnect from pyVmomi import vim CRD_GROUP = 'test.local' CRD_VERSION = 'v1' CRD_PLURAL = 'tenants' C_CRD_PLURAL = 'vms' C_CRD_KIND = 'VM' def decode_secret(s): return base64.b64decode(s).decode() @kopf.on.create(CRD_GROUP, CRD_VERSION, CRD_PLURAL) def create_fn(body, **kwargs): common_fn(body, **kwargs) @kopf.on.update(CRD_GROUP, CRD_VERSION, CRD_PLURAL) def update_fn(body, **kwargs): common_fn(body, **kwargs) def common_fn(body, **kwargs): kapi = kubernetes.client.CoreV1Api() capi = kubernetes.client.CustomObjectsApi() exec_flag = body.get('execFlag', True) if exec_flag is not False: name = body['metadata']['name'] kopf.info(body, reason='name', message=name) namespace = body['metadata']['namespace'] kopf.info(body, reason='namespace', message=namespace) target_namespace = body['namespace'] kopf.info(body, reason='target_namespace', message=target_namespace) username = body['username'] kopf.info(body, reason='username', message=username) password_name = body['passwordName'] res = kapi.read_namespaced_secret(password_name, namespace, exact=True, export=True) password = decode_secret(res.data['password']) #--- vCenter connection si = SmartConnect(host=getenv('VCENTER_HOST'), user=username, pwd=password, ) if not si: kopf.warn(body, reason='auth', message="connection error") msg = f"VM info created by Tenant" return {'message': msg} atexit.register(Disconnect, si) #=== vCenter connection access_date = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') kopf.info(body, reason='access-date', message=access_date) dc = si.content.rootFolder.childEntity[0] folders = dc.vmFolder.childEntity kopf.info(body, reason='dc', message=dc.name) for folder in folders: kopf.info(body, reason='folder', message=folder.name) vms = folder.childEntity for vm in vms: kopf.info(body, reason='vm', message=vm.name) summary = vm.summary vmname = vm.config.name template = vm.config.template ip = vm.guest.ipAddress if ip is None: ip = [] elif type(ip) is not list: ip = [ip] power_state = vm.runtime.powerState num_cpu = vm.config.hardware.numCPU memory_mb = vm.config.hardware.memoryMB info = { 'vmname': vmname, 'powerState': power_state, 'numCpu': num_cpu, 'memoryMB': memory_mb, 'ip': ip, } kopf.info(body, reason='vm-info', message=json.dumps(info)) rec = { 'apiVersion': f"{CRD_GROUP}/{CRD_VERSION}", 'kind': C_CRD_KIND, 'metadata': { "name": vmname, "namespace": target_namespace, }, 'updateDate': access_date, } rec.update(info) obj = { 'group': CRD_GROUP, 'version': CRD_VERSION, 'plural': C_CRD_PLURAL, 'namespace': target_namespace, 'body': rec, } try: capi.create_namespaced_custom_object(**obj) except Exception as e: kopf.info(body, reason='vm-info-creation-error', message=str(e)) obj['name'] = obj['body']['metadata']['name'] capi.patch_namespaced_custom_object(**obj) ubody = body ubody['execFlag'] = False ubody['execDate'] = access_date meta = { 'labels': ubody['metadata']['labels'], } ubody['metadata'] = meta obj = { 'group': CRD_GROUP, 'version': CRD_VERSION, 'plural': CRD_PLURAL, 'name': name, 'namespace': namespace, 'body': ubody, } kopf.info(body, reason='req-payload', message=json.dumps(obj)) capi.patch_namespaced_custom_object(**obj)
import base64
import yaml
import json
import datetime
import atexit
from os import getenv
 
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
 
import kopf
import kubernetes
 
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim
 
 
CRD_GROUP = 'test.local'
CRD_VERSION = 'v1'
CRD_PLURAL = 'tenants'
C_CRD_PLURAL = 'vms'
C_CRD_KIND = 'VM'
 
 
def decode_secret(s):
    return base64.b64decode(s).decode()
 
 
@kopf.on.create(CRD_GROUP, CRD_VERSION, CRD_PLURAL)
def create_fn(body, **kwargs):
    common_fn(body, **kwargs)
 
 
@kopf.on.update(CRD_GROUP, CRD_VERSION, CRD_PLURAL)
def update_fn(body, **kwargs):
    common_fn(body, **kwargs)
 
 
def common_fn(body, **kwargs):
    kapi = kubernetes.client.CoreV1Api()
    capi = kubernetes.client.CustomObjectsApi()
 
    exec_flag = body.get('execFlag', True)
    if exec_flag is not False:
        name = body['metadata']['name']
        kopf.info(body, reason='name', message=name)
        namespace = body['metadata']['namespace']
        kopf.info(body, reason='namespace', message=namespace)
        target_namespace = body['namespace']
        kopf.info(body, reason='target_namespace', message=target_namespace)
        username = body['username']
        kopf.info(body, reason='username', message=username)
        password_name = body['passwordName']
        res = kapi.read_namespaced_secret(password_name, namespace, exact=True, export=True)
        password = decode_secret(res.data['password'])
 
#--- vCenter connection
        si = SmartConnect(host=getenv('VCENTER_HOST'),
                          user=username,
                          pwd=password,
                         )
 
        if not si:
            kopf.warn(body, reason='auth', message="connection error")
            msg = f"VM info created by Tenant"
            return {'message': msg}
 
        atexit.register(Disconnect, si)
#=== vCenter connection
 
        access_date = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
        kopf.info(body, reason='access-date', message=access_date)
 
        dc = si.content.rootFolder.childEntity[0]
        folders = dc.vmFolder.childEntity
 
        kopf.info(body, reason='dc', message=dc.name)
        for folder in folders:
            kopf.info(body, reason='folder', message=folder.name)
            vms = folder.childEntity
            for vm in vms:
                kopf.info(body, reason='vm', message=vm.name)
                summary = vm.summary
                vmname = vm.config.name
                template = vm.config.template
                ip = vm.guest.ipAddress
                if ip is None:
                    ip = []
                elif type(ip) is not list:
                    ip = [ip]
                power_state = vm.runtime.powerState
                num_cpu = vm.config.hardware.numCPU
                memory_mb = vm.config.hardware.memoryMB
                info = {
                        'vmname': vmname,
                        'powerState': power_state,
                        'numCpu': num_cpu,
                        'memoryMB': memory_mb,
                        'ip': ip,
                       }
                kopf.info(body, reason='vm-info', message=json.dumps(info))
                rec = {
                       'apiVersion': f"{CRD_GROUP}/{CRD_VERSION}",
                       'kind': C_CRD_KIND,
                       'metadata': {
                                    "name": vmname,
                                    "namespace": target_namespace,
                                   },
                       'updateDate': access_date,
                      }
                rec.update(info)
                obj = {
                       'group': CRD_GROUP,
                       'version': CRD_VERSION,
                       'plural': C_CRD_PLURAL,
                       'namespace': target_namespace,
                       'body': rec,
                      }
 
                try:
                    capi.create_namespaced_custom_object(**obj)
                except Exception as e:
                    kopf.info(body, reason='vm-info-creation-error', message=str(e))
                    obj['name'] = obj['body']['metadata']['name']
                    capi.patch_namespaced_custom_object(**obj)
 
        ubody = body
        ubody['execFlag'] = False
        ubody['execDate'] = access_date
        meta = {
                'labels': ubody['metadata']['labels'],
               }
        ubody['metadata'] = meta
        obj = {
               'group': CRD_GROUP,
               'version': CRD_VERSION,
               'plural': CRD_PLURAL,
               'name': name,
               'namespace': namespace,
               'body': ubody,
              }
        kopf.info(body, reason='req-payload', message=json.dumps(obj))
        capi.patch_namespaced_custom_object(**obj)

Operatorのデプロイ

Operatorにしかるべき権限を与えるために、サービスアカウントを作成しRBACに則ってロールを付加します。(https://kubernetes.io/docs/reference/access-authn-authz/rbac/

作りこむ手間を省くため、cluster-adminをとりあえず与えます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
apiVersion: v1
kind: ServiceAccount
metadata:
name: tenant-operator
apiVersion: v1 kind: ServiceAccount metadata: name: tenant-operator
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tenant-operator
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tenant-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: tenant-operator
namespace: default
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: tenant-operator roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: tenant-operator namespace: default
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tenant-operator
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tenant-operator
    namespace: default
  • 背景補足
    • vCenterのURLは環境変数経由で与えます。
    • Kubernetesクラスタ内にコンテナレジストリがデプロイしてあります。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
apiVersion: apps/v1
kind: Deployment
metadata:
name: tenant-op
spec:
selector:
matchLabels:
app: tenant-op
template:
metadata:
labels:
app: tenant-op
spec:
serviceAccountName: tenant-operator
containers:
- image: localhost:30852/op-tenant:latest
name: tenant-op
env:
- name: VCENTER_HOST
value: nwcd-vmw-vcenter1500-stg
apiVersion: apps/v1 kind: Deployment metadata: name: tenant-op spec: selector: matchLabels: app: tenant-op template: metadata: labels: app: tenant-op spec: serviceAccountName: tenant-operator containers: - image: localhost:30852/op-tenant:latest name: tenant-op env: - name: VCENTER_HOST value: nwcd-vmw-vcenter1500-stg
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tenant-op
spec:
  selector:
    matchLabels:
      app: tenant-op
  template:
    metadata:
      labels:
        app: tenant-op
    spec:
      serviceAccountName: tenant-operator
      containers:
      - image: localhost:30852/op-tenant:latest
        name: tenant-op
        env:
        - name: VCENTER_HOST
          value: nwcd-vmw-vcenter1500-stg

結果

以上で、kubectlで仮想マシンのリストを見るための準備はできました。

構成要素

以下の2つがOperator関連の構成要素です。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# kubectl get serviceaccounts
NAME SECRETS AGE
default 1 51d
tenant-operator 1 2d22h
# kubectl get serviceaccounts NAME SECRETS AGE default 1 51d tenant-operator 1 2d22h
# kubectl get serviceaccounts
NAME              SECRETS   AGE
default           1         51d
tenant-operator   1         2d22h
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# kubectl get deploy tenant-op
NAME READY UP-TO-DATE AVAILABLE AGE
tenant-op 1/1 1 1 2d22h
# kubectl get po -l app=tenant-op
NAME READY STATUS RESTARTS AGE
tenant-op-6864547fb4-s4tmj 1/1 Running 0 32m
# kubectl get deploy tenant-op NAME READY UP-TO-DATE AVAILABLE AGE tenant-op 1/1 1 1 2d22h # kubectl get po -l app=tenant-op NAME READY STATUS RESTARTS AGE tenant-op-6864547fb4-s4tmj 1/1 Running 0 32m
# kubectl get deploy tenant-op
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
tenant-op   1/1     1            1           2d22h
  
# kubectl get po -l app=tenant-op
NAME                         READY   STATUS    RESTARTS   AGE
tenant-op-6864547fb4-s4tmj   1/1     Running   0          32m

テナント情報

次にテナントの認証情報です。これらが投入されることをトリガーに、仮想マシンについての情報がKubernetesに収集されます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# kubectl get secrets test-pass
NAME TYPE DATA AGE
test-pass Opaque 1 2d21h
# kubectl get secrets test-pass NAME TYPE DATA AGE test-pass Opaque 1 2d21h
# kubectl get secrets test-pass
NAME        TYPE     DATA   AGE
test-pass   Opaque   1      2d21h
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
apiVersion: test.local/v1
kind: Tenant
metadata:
name: test
labels:
module: tenant
namespace: test-tenant
username: ike-admin@vsphere.local
passwordName: test-pass
apiVersion: test.local/v1 kind: Tenant metadata: name: test labels: module: tenant namespace: test-tenant username: ike-admin@vsphere.local passwordName: test-pass
apiVersion: test.local/v1
kind: Tenant
metadata:
  name: test
  labels:
    module: tenant
namespace: test-tenant
username: ike-admin@vsphere.local
passwordName: test-pass

テナントCRDを投入すると…

ike-admin@vsphere.localというテナントがtenantリソースに登録されていることが確認できます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# kubectl get tenant
NAME USERNAME TARGETNAMESPACE EXECDATE EXECFLAG AGE
test ike-admin@vsphere.local test-tenant 4s false 4s
# kubectl get tenant NAME USERNAME TARGETNAMESPACE EXECDATE EXECFLAG AGE test ike-admin@vsphere.local test-tenant 4s false 4s
# kubectl get tenant
NAME   USERNAME                  TARGETNAMESPACE   EXECDATE   EXECFLAG   AGE
test   ike-admin@vsphere.local   test-tenant       4s         false      4s

そして、test-tenantのnamespaceにこのテナントから見える仮想マシンの情報が一覧化されます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# kubectl get vm -n test-tenant
NAME POWERSTATE NUMCPU MEMORYMB IPS UPDATEDATE AGE
nwcd-ike-a-m1 poweredOn 2 2048 [10.141.86.60] 47s 47s
nwcd-ike-a-w1 poweredOn 2 2048 [10.141.86.70] 47s 47s
nwcd-ike-admin1500-stg poweredOn 2 8192 [10.141.86.11] 47s 46s
nwcd-ike-centos poweredOn 2 8192 [10.141.86.23] 47s 46s
nwcd-ike-clean poweredOff 2 8192 [] 47s 46s
nwcd-ike-gate1500-stg poweredOn 1 1024 [10.141.86.10] 47s 47s
nwcd-ike-router1500-stg poweredOn 1 512 [10.141.86.200] 47s 46s
nwcd-ike-template1500-stg poweredOff 2 8192 [] 47s 46s
nwcd-ike-ubuntu-base poweredOff 2 2048 [] 47s 46s
nwcd-ike-ubuntu-template poweredOn 2 2048 [10.141.86.20] 47s 46s
nwcd-ike-ubuntu-template-stg poweredOff 2 2048 [] 47s 46s
# kubectl get vm -n test-tenant NAME POWERSTATE NUMCPU MEMORYMB IPS UPDATEDATE AGE nwcd-ike-a-m1 poweredOn 2 2048 [10.141.86.60] 47s 47s nwcd-ike-a-w1 poweredOn 2 2048 [10.141.86.70] 47s 47s nwcd-ike-admin1500-stg poweredOn 2 8192 [10.141.86.11] 47s 46s nwcd-ike-centos poweredOn 2 8192 [10.141.86.23] 47s 46s nwcd-ike-clean poweredOff 2 8192 [] 47s 46s nwcd-ike-gate1500-stg poweredOn 1 1024 [10.141.86.10] 47s 47s nwcd-ike-router1500-stg poweredOn 1 512 [10.141.86.200] 47s 46s nwcd-ike-template1500-stg poweredOff 2 8192 [] 47s 46s nwcd-ike-ubuntu-base poweredOff 2 2048 [] 47s 46s nwcd-ike-ubuntu-template poweredOn 2 2048 [10.141.86.20] 47s 46s nwcd-ike-ubuntu-template-stg poweredOff 2 2048 [] 47s 46s
# kubectl get vm -n test-tenant
NAME                                        POWERSTATE   NUMCPU   MEMORYMB   IPS               UPDATEDATE   AGE
nwcd-ike-a-m1                               poweredOn    2        2048       [10.141.86.60]    47s          47s
nwcd-ike-a-w1                               poweredOn    2        2048       [10.141.86.70]    47s          47s
nwcd-ike-admin1500-stg                      poweredOn    2        8192       [10.141.86.11]    47s          46s
nwcd-ike-centos                             poweredOn    2        8192       [10.141.86.23]    47s          46s
nwcd-ike-clean                              poweredOff   2        8192       []                47s          46s
nwcd-ike-gate1500-stg                       poweredOn    1        1024       [10.141.86.10]    47s          47s
nwcd-ike-router1500-stg                     poweredOn    1        512        [10.141.86.200]   47s          46s
nwcd-ike-template1500-stg                   poweredOff   2        8192       []                47s          46s
nwcd-ike-ubuntu-base                        poweredOff   2        2048       []                47s          46s
nwcd-ike-ubuntu-template                    poweredOn    2        2048       [10.141.86.20]    47s          46s
nwcd-ike-ubuntu-template-stg                poweredOff   2        2048       []                47s          46s

例えばCPU数順に表示することも簡単です。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# kubectl get vm -n test-tenant --sort-by='.numCpu'
NAME POWERSTATE NUMCPU MEMORYMB IPS UPDATEDATE AGE
nwcd-ike-router1500-stg poweredOn 1 512 [10.141.86.200] 16d 16d
nwcd-ike-gate1500-stg.hop.2iij.net poweredOn 1 1024 [10.141.86.10] 16d 16d
nwcd-ike-a-w1 poweredOn 2 2048 [10.141.86.70] 16d 16d
nwcd-ike-admin1500-stg poweredOn 2 8192 [10.141.86.11] 16d 16d
nwcd-ike-centos poweredOn 2 8192 [10.141.86.23] 16d 16d
nwcd-ike-clean poweredOff 2 8192 [] 16d 16d
nwcd-ike-a-m1 poweredOn 2 2048 [10.141.86.60] 16d 16d
nwcd-ike-template1500-stg poweredOff 2 8192 [] 16d 16d
nwcd-ike-ubuntu-base poweredOff 2 2048 [] 16d 16d
nwcd-ike-ubuntu-template poweredOn 2 2048 [10.141.86.20] 16d 16d
nwcd-ike-ubuntu-template-stg.hop.2iij.net poweredOff 2 2048 [] 16d 16d
# kubectl get vm -n test-tenant --sort-by='.numCpu' NAME POWERSTATE NUMCPU MEMORYMB IPS UPDATEDATE AGE nwcd-ike-router1500-stg poweredOn 1 512 [10.141.86.200] 16d 16d nwcd-ike-gate1500-stg.hop.2iij.net poweredOn 1 1024 [10.141.86.10] 16d 16d nwcd-ike-a-w1 poweredOn 2 2048 [10.141.86.70] 16d 16d nwcd-ike-admin1500-stg poweredOn 2 8192 [10.141.86.11] 16d 16d nwcd-ike-centos poweredOn 2 8192 [10.141.86.23] 16d 16d nwcd-ike-clean poweredOff 2 8192 [] 16d 16d nwcd-ike-a-m1 poweredOn 2 2048 [10.141.86.60] 16d 16d nwcd-ike-template1500-stg poweredOff 2 8192 [] 16d 16d nwcd-ike-ubuntu-base poweredOff 2 2048 [] 16d 16d nwcd-ike-ubuntu-template poweredOn 2 2048 [10.141.86.20] 16d 16d nwcd-ike-ubuntu-template-stg.hop.2iij.net poweredOff 2 2048 [] 16d 16d
# kubectl get vm -n test-tenant --sort-by='.numCpu'
NAME                                        POWERSTATE   NUMCPU   MEMORYMB   IPS               UPDATEDATE   AGE
nwcd-ike-router1500-stg                     poweredOn    1        512        [10.141.86.200]   16d          16d
nwcd-ike-gate1500-stg.hop.2iij.net          poweredOn    1        1024       [10.141.86.10]    16d          16d
nwcd-ike-a-w1                               poweredOn    2        2048       [10.141.86.70]    16d          16d
nwcd-ike-admin1500-stg                      poweredOn    2        8192       [10.141.86.11]    16d          16d
nwcd-ike-centos                             poweredOn    2        8192       [10.141.86.23]    16d          16d
nwcd-ike-clean                              poweredOff   2        8192       []                16d          16d
nwcd-ike-a-m1                               poweredOn    2        2048       [10.141.86.60]    16d          16d
nwcd-ike-template1500-stg                   poweredOff   2        8192       []                16d          16d
nwcd-ike-ubuntu-base                        poweredOff   2        2048       []                16d          16d
nwcd-ike-ubuntu-template                    poweredOn    2        2048       [10.141.86.20]    16d          16d
nwcd-ike-ubuntu-template-stg.hop.2iij.net   poweredOff   2        2048       []                16d          16d

起動している仮想マシンが7台と、起動していない仮想マシンが4台あることも確認できます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# kubectl get vm -n test-tenant | grep poweredOn | wc -l
7
# kubectl get vm -n test-tenant | grep poweredOff | wc -l
4
# kubectl get vm -n test-tenant | grep poweredOn | wc -l 7 # kubectl get vm -n test-tenant | grep poweredOff | wc -l 4
# kubectl get vm -n test-tenant | grep poweredOn | wc -l
7
  
# kubectl get vm -n test-tenant | grep poweredOff | wc -l
4

その他便利機能及び拡張

Kubernetesの機能を用いることで、構成の変更内容を確認することができます。

例えば、nwcd-ike-a-m1のCPU数を変更してみると、以下のような差分を表示することができます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# cat mod-vm.yaml
apiVersion: test.local/v1
kind: VM
metadata:
name: nwcd-ike-a-m1
namespace: test-tenant
vmname: nwcd-ike-a-m1
numCpu: 4
memoryMB: 2048
powerState: poweredOn
ip:
- 10.141.86.60
# kubectl -n test-tenant diff -f mod-vm.yaml
diff -u -N /tmp/LIVE-707611086/test.local.v1.VM.test-tenant.nwcd-ike-a-m1 /tmp/MERGED-244975829/test.local.v1.VM.test-tenant.nwcd-ike-a-m1
--- /tmp/LIVE-707611086/test.local.v1.VM.test-tenant.nwcd-ike-a-m1 2019-12-12 13:34:39.002426608 +0900
+++ /tmp/MERGED-244975829/test.local.v1.VM.test-tenant.nwcd-ike-a-m1 2019-12-12 13:34:39.017426607 +0900
@@ -5,13 +5,13 @@
memoryMB: 2048
metadata:
creationTimestamp: "2019-11-25T13:54:09Z"
- generation: 1
+ generation: 2
name: nwcd-ike-a-m1
namespace: test-tenant
resourceVersion: "8255884"
selfLink: /apis/test.local/v1/namespaces/test-tenant/vms/nwcd-ike-a-m1
uid: 0aa83184-9f8c-4256-99c9-466a8b7dff18
-numCpu: 2
+numCpu: 4
powerState: poweredOn
updateDate: "2019-11-25T13:54:09Z"
vmname: nwcd-ike-a-m1
# cat mod-vm.yaml apiVersion: test.local/v1 kind: VM metadata: name: nwcd-ike-a-m1 namespace: test-tenant vmname: nwcd-ike-a-m1 numCpu: 4 memoryMB: 2048 powerState: poweredOn ip: - 10.141.86.60 # kubectl -n test-tenant diff -f mod-vm.yaml diff -u -N /tmp/LIVE-707611086/test.local.v1.VM.test-tenant.nwcd-ike-a-m1 /tmp/MERGED-244975829/test.local.v1.VM.test-tenant.nwcd-ike-a-m1 --- /tmp/LIVE-707611086/test.local.v1.VM.test-tenant.nwcd-ike-a-m1 2019-12-12 13:34:39.002426608 +0900 +++ /tmp/MERGED-244975829/test.local.v1.VM.test-tenant.nwcd-ike-a-m1 2019-12-12 13:34:39.017426607 +0900 @@ -5,13 +5,13 @@ memoryMB: 2048 metadata: creationTimestamp: "2019-11-25T13:54:09Z" - generation: 1 + generation: 2 name: nwcd-ike-a-m1 namespace: test-tenant resourceVersion: "8255884" selfLink: /apis/test.local/v1/namespaces/test-tenant/vms/nwcd-ike-a-m1 uid: 0aa83184-9f8c-4256-99c9-466a8b7dff18 -numCpu: 2 +numCpu: 4 powerState: poweredOn updateDate: "2019-11-25T13:54:09Z" vmname: nwcd-ike-a-m1
# cat mod-vm.yaml
apiVersion: test.local/v1
kind: VM
metadata:
  name: nwcd-ike-a-m1
  namespace: test-tenant
vmname: nwcd-ike-a-m1
numCpu: 4
memoryMB: 2048
powerState: poweredOn
ip:
- 10.141.86.60
  
# kubectl -n test-tenant diff -f mod-vm.yaml
diff -u -N /tmp/LIVE-707611086/test.local.v1.VM.test-tenant.nwcd-ike-a-m1 /tmp/MERGED-244975829/test.local.v1.VM.test-tenant.nwcd-ike-a-m1
--- /tmp/LIVE-707611086/test.local.v1.VM.test-tenant.nwcd-ike-a-m1      2019-12-12 13:34:39.002426608 +0900
+++ /tmp/MERGED-244975829/test.local.v1.VM.test-tenant.nwcd-ike-a-m1    2019-12-12 13:34:39.017426607 +0900
@@ -5,13 +5,13 @@
 memoryMB: 2048
 metadata:
   creationTimestamp: "2019-11-25T13:54:09Z"
-  generation: 1
+  generation: 2
   name: nwcd-ike-a-m1
   namespace: test-tenant
   resourceVersion: "8255884"
   selfLink: /apis/test.local/v1/namespaces/test-tenant/vms/nwcd-ike-a-m1
   uid: 0aa83184-9f8c-4256-99c9-466a8b7dff18
-numCpu: 2
+numCpu: 4
 powerState: poweredOn
 updateDate: "2019-11-25T13:54:09Z"
 vmname: nwcd-ike-a-m1

差分に問題がないことを確認してから投入すれば、想定外の構成変更を実行してしまうことを防ぐことができます。また、VMリソースの変更に対するOperatorを実装することで、Kubernetes側で管理しているデータベースの内容をvCenterに反映させることもできるようになります。

万が一、typoをしてメモリの0(ゼロ)をo(オー)と書いてしまっても、型チェックをしてくれます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# cat mod-vm.yaml
apiVersion: test.local/v1
kind: VM
metadata:
name: nwcd-ike-a-m1
namespace: test-tenant
vmname: nwcd-ike-a-m1
numCpu: 4
memoryMB: 2o48
powerState: poweredOn
ip:
- 10.141.86.60
# kubectl -n test-tenant diff -f mod-vm.yaml
The VM "nwcd-ike-a-m1" is invalid: memoryMB: Invalid value: "string": memoryMB in body must be of type number: "string"
# cat mod-vm.yaml apiVersion: test.local/v1 kind: VM metadata: name: nwcd-ike-a-m1 namespace: test-tenant vmname: nwcd-ike-a-m1 numCpu: 4 memoryMB: 2o48 powerState: poweredOn ip: - 10.141.86.60 # kubectl -n test-tenant diff -f mod-vm.yaml The VM "nwcd-ike-a-m1" is invalid: memoryMB: Invalid value: "string": memoryMB in body must be of type number: "string"
# cat mod-vm.yaml
apiVersion: test.local/v1
kind: VM
metadata:
  name: nwcd-ike-a-m1
  namespace: test-tenant
vmname: nwcd-ike-a-m1
numCpu: 4
memoryMB: 2o48
powerState: poweredOn
ip:
- 10.141.86.60
 
 
# kubectl -n test-tenant diff -f mod-vm.yaml
The VM "nwcd-ike-a-m1" is invalid: memoryMB: Invalid value: "string": memoryMB in body must be of type number: "string"

他にも例えば、CronJobで定期的にtenantリソースのEXECFLAGをtrueにすることで、取得情報を同期させることができます。

まとめ

以上でvCenterにある情報を手軽にKubernetesに保管して構成管理する方法を紹介しました。

Kubernetesを使うことによって、

  • スキーマが定義されるため、構成管理の内容についてバリデーションをかけることができます
  • kubectlによる一覧・編集などの制御ができます
  • Operatorの開発により、運用の自動化を進めることができます

本記事では高々150行ほどのコードで、テナントユーザの情報からそのテナントが管理している仮想マシンの情報を一覧化することができました。

※本当は構成管理側の設定内容を反映させることが構成管理になりますが、今回はすでに動いている既存のシステムの情報をインポートする部分をカバーしました。

李 瀚

2019年12月18日 水曜日

締め切りドリブンな生活から脱却したい

Related
関連記事