KubernetesのOperatorをAnsibleで作るときのTips
この記事は、Kubernetes3 Advent Calendarの6日目の記事です。
Kubernetesのoperatorがansibleでも書けるようになっていますね。 インフラエンジニアであるところの私にとっては、とても嬉しいところで、golangは手も足も出ませんが、ansibleならどんと来いって気持ちになれます。
Ansibleでoperatorを書く記事は、結構出回っていて、特に Ansible Operator Overviewは、デモ用の環境で、実際に手を動かしながら、ansibleのoperatorを作成できるのでオススメです。
↑ のデモを一通りやった方が、実際にoperatorを作ってみよう、となったときに役立ちそうなTipsをご紹介します。(一部デモと内容重複もあります)
この記事では、↑ のデモでも使っているoperator-sdkを使っています。operator-sdkのインストールがまだの方は、Install the Operator SDK CLIを参考に準備をお願いします。
まずoperatorの枠を作っておきます。
$ operator-sdk new imo-operator --api-version=imo.example.com/v1alpha1 --kind=Imo --type=ansible$ cd imo-operatorOperatorのbase image
operator-sdk newで出来上がった枠で使っているoperatorのimageは最新のstableになっていると思います。ただ、それより新しい開発真っ只中のを指定することができます。Operatorは開発がどんどん進んでおり、新しい機能が使えますので、ぜひ最新のイメージにしましょう。
diff --git a/build/Dockerfile b/build/Dockerfileindex 47dc7fb..f916d7d 100644--- a/build/Dockerfile+++ b/build/Dockerfile@@ -1,4 +1,4 @@-FROM quay.io/operator-framework/ansible-operator:v0.12.0+FROM quay.io/operator-framework/ansible-operator:master
COPY watches.yaml ${HOME}/watches.yamlたとえば、 watches.yamlからansibleの変数を渡したりするのは、この最新イメージでないとできないです。。(今のところ)
2019/12/13 追記: 0.13.0 がリリースされて、そのリリースに↑は含まれています。
定期的なreconcileを実行する
watches.yaml で以下のように指定すると、Resourceの変化があったときだけではなく、定期的にreconcileを実行することが可能です。
diff --git a/watches.yaml b/watches.yamlindex 872bf2f..cfee326 100644--- a/watches.yaml+++ b/watches.yaml@@ -3,3 +3,4 @@ group: imo.example.com kind: Imo role: /opt/ansible/roles/imo+ reconcilePeriod: 30s定期的なreconcileを実行するには、例えば以下のような方法があるかと思います。
- OperatorでCronJobを作成する
- OperatorでJobを作成(完了したら削除)する。
- Operatorで(kubernetesのobjectを使わない)ansibleのplaybookを書く
定期的なタスクと言っても、時には実行してほしくないことや、ある程度条件を整えてから実行したいなど、よくあるかなと思います。1.のCronJobを使うケースではそういった場合の処理もすべてCronJobの中で扱えるようにしておく必要があり、マイクロサービス的な考えからするとあまり感触がよくなさそうな感じがします。
2.の場合は、Jobのデプロイ前後などで、operator側で状態の確認などの処理をしておくことができます。(この処理はansibleで書けます)
2.と3.は個人的にはどちらでも良いのかなと思っていまして、状況によりよりよい方法を使うのが良いかなと思います。
↓ のレポジトリに、2. の方法のサンプルのroleを置いておきましたので、よかったら参考になさってください。
Kubernetes Operator built with Ansible Example
- debug: var: operations
- import_tasks: cleanup.yaml when: ('cleanup' in operations) tags: - cleanup
- import_tasks: deploy.yaml when: ('deploy' in operations) tags: - deploycleanup.yaml でJobの削除、deploy.yaml でJobの作成を行っています。
ポイントは削除のときに、JobとPodを両方明示的に削除していることです。(どちらかだけだと、もう一方が残ってしまう)
Resourceの状態を任意の値にする
Reconcileは定期的なサイクルや、Resourceの状態に応じて実行されますが、自分でResourceの状態をコントロールすることもできます。
Resourceの状態をセットするには ansibleの k8s_status モジュールを使って、playbookの中で以下のように実行します。
2019/12/13 追記:
k8s_statusモジュールは削除され、代わりに、operator_sdk.util.k8s_statusを使うようになっています。詳しくは Remove k8s_status module, install from galaxy
- k8s_status: api_version: kabocha.example.com/v1alpha1 kind: Kabocha name: "{{ meta.name }}" namespace: "{{ meta.namespace }}" status: phase: "{{ phase }}"このときに、kubernetesによる状態の管理をしてほしくない(勝手にreconcileが動いてほしくない)場合は、watches.yaml に manageStatus: false を入れておきます。
- version: v1alpha1 group: kabocha.example.com kind: Kabocha reconcilePeriod: 90s manageStatus: false role: /opt/ansible/roles/kabocha vars: phase: - setupすると、CustomResourceをdescribeしたときに、以下のような表示となり、手動でセットしたstatusが表示されます。
k8s_infoモジュールを使うとplaybookの中でもこの情報が取れるので、statusに応じた処理を書くことができます。
Name: example-kabochaNamespace: imokuriLabels: <none>Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"kabocha.example.com/v1alpha1","kind":"Kabocha","metadata":{"annotations":{},"name":"example-kabocha","namespace":"imokuri"}...API Version: kabocha.example.com/v1alpha1Kind: KabochaMetadata: Creation Timestamp: 2019-12-03T12:55:09Z Finalizers: finalizer.kabocha.example.com Generation: 1 Resource Version: 10700874 Self Link: /apis/kabocha.example.com/v1alpha1/namespaces/imokuri/kabochas/example-kabocha UID: 4d79c40b-44cf-4ee6-bf84-2fc48e0cd552Spec: Dummy: Imo, Kuri and KabochaStatus: Phase: setup # ここEvents: <none>このstatusは通常は以下のように Conditions が入っていますが、 manageStatus: false を指定するとこれらが表示(管理)されなくなります。
Status: Conditions: Ansible Result: Changed: 0 Completion: 2019-12-03T12:54:32.87886 Failures: 0 Ok: 3 Skipped: 0 Last Transition Time: 2019-12-03T12:53:48Z Message: Awaiting next reconciliation Reason: Successful Status: True Type: Runningk8s_statusモジュールでこのConditions配下を変更することも可能ですが、いくつか条件があります。
Type: 必須Status: 必須(True,False,Unknownのどれか)Reason: CamelCaseの1単語
などです。(詳しくはk8s_statusのマニュアルを参照ください)
またConditionsを手動で変更すると、その後は自動でのstatusの更新は行われなくなりますので、特段の理由がない限りは、Conditions配下のパラメータは使用せず、Status直下にカスタムのパラメータを配置するのが良いと思います。
Operatorの階層構造
Ansibleではもともとroleを分割したりすることで階層構造を作れる仕組みがあります。 この場合は、1つのCustomResourceで処理が作れることになり、Reconcileの単位もこの単位になります。
一方で、Operatorでは、CustomResourceを複数作ることで階層構造を作ることができます。 この場合は、Reconcileの単位は、CustomResourceの単位になるので、 実施させたい処理によって、より良い方を選ぶのが良いと思います。
参考までに、CustomResourceを分けた場合の例を以下にアップしてあります。
Kubernetes Operator built with Ansible Example
↑ では分割したCustomResourceをまとめて起動するCustomResourceを用意しています。
この階層構造を作るときの注意として、変数で定義した値が(intで読まれてほしいときでも)stringとなってしまうことがあり、objectの作成に失敗することがあります。 その場合は、k8sモジュールのタスクで直接manifestを書くのではなく、templateファイルを作成して、それを読み込ませるようにすると、回避できます。
tasks/main.yaml
---# templateから読まないとreplicasがstring扱いとなってobjectが作成できない- name: Deploy Imo k8s: state: "{{ state | default(omit) }}" definition: "{{ lookup('template', 'deploy.yaml') }}"templates/deploy.yaml
---kind: DeploymentapiVersion: apps/v1metadata: name: "{{ meta.name }}" namespace: "{{ meta.namespace }}"spec: # ↓ ダブルクォートで囲まないこと replicas: { { size | int } } selector: matchLabels: app: imo template: metadata: labels: app: imo spec: containers: - name: imo image: nginx ports: - containerPort: 80Ansibleのplaybookを実行するための依存パッケージを入れたい
Operatorビルド用のDockerfile(build/Dockerfile)の中で以下の要領で、パッケージを追加できます。
USER 0RUN yum -y install my-dependencyRUN pip3 install my-python-dependencyUSER 1001まとめ
いかがでしたでしょうか。ansibleが書ける方なら、割と複雑な運用もoperatorに任せられるようになるのではないかなと思います。 ぜひインフラなみなさんもKubernetes & Operatorを使っていきましょう。(^^)
今回の検証で使用したコードは以下のレポジトリにアップしています。
Kubernetes Operator built with Ansible Example
参照: