PythonやJavaScript、C#などでクラウドインフラを定義できる構成管理ツール「Pulumi」を使ってみる
昨今では、IaaSやPaaSといったさまざまなクラウドサービスが多くのシステムで使われている。こうしたクラウドサービスの多くはAPIを使ったインフラの操作をサポートしており、それらを活用するさまざまな支援ツールが登場している。今回はそういったツールの1つで、さまざまなプログラミング言語を使ってクラウド上のリソースを管理できる「Pulumi」を紹介する。
目次
さまざまな言語でさまざまなクラウドプラットフォームを操作可能
昨今ではInfrastrcture as a Service(IaaS)やPaaS(Platform as a Service)といった、ソフトウェアを動かすためのインフラストラクチャ(インフラ)やプラットフォームを提供するクラウドサービスが広く普及している。こういったクラウドサービスには「使った分だけの料金支払いで済む」「ソフトウェアの実行環境や各種インフラを迅速に調達できる」といった利点に加えて、外部からインフラの操作を行えるAPIを利用してさまざまな処理を自動化できるという特徴もある。
そして、今日ではこうしたクラウドプラットフォームが提供するAPIを活用するツールが登場している。こういったAPIを活用するツールにはクラウド上のリソースを管理するものや監視を行うもの、ほかのサービスとの連携を行うものなどさまざまなものがあるが、その1つに設定管理のためのツール「Terraform」がある。Terraformについてはさくらのナレッジでも入門記事が公開されているが、簡単に言うと独自の書式でインフラの状態を記述することで、クラウド上のリソースをそれにあわせて自動的に作成できるというツールだ。
Terraformはすでに多くのユーザーを獲得しており、広く活用されているが、今回紹介するPulumiも、Terraformと同様にファイルに記述した情報を元にクラウド上のリソースを作成してくれるツールだ(図1)。
クラウド上のリソースの状態をファイルに記述してPulumiを実行すると、その通りの構成になるようリソースを自動的に作成してくれる、という点ではPulumiはTerraformと同じなのだが、Pulumiはリソースの状態を設定ファイルではなくプログラムとして定義する点が、Terraformとの大きな違いとなっている。
プログラミング言語を使ってインフラを記述するメリット
Pulumiでは現在JavaScriptおよびTypeScript、Pythonでのリソース記述をサポートしているほか、GoやC#についてもプレビューという段階でサポートが進められている。Pulumiではこれらの言語でリソースを定義するためのモジュール(ライブラリ)が提供されており、ソースコード中でこのモジュール(ライブラリ)を読み込み、作成したいリソースに対応するオブジェクトを作成することで作成するリソースやそれらの構成を定義するようになっている。
なお、Pulumiのモジュールが提供するのはクラウド上のリソースを定義する方法だけであって、そのモジュールによって直接リソースが作成されるわけではない。あくまでリソースを作成するのはPulumi本体であり、プログラムの記述後に「pulumi」コマンドを実行することで対応するリソースがクラウド上に作成されるようになっている。
このように独自の設定ファイルではなく、汎用のプログラミング言語を使ってインフラを記述できる点には、次のようなメリットがある。
- ツール独自の設定ファイル文法などを覚える必要がない
- 条件分岐などの処理を柔軟に記述できる
- 外部アプリケーションや外部のデータとの連係をしやすい
- プログラミング言語向けのツールによるサポートが期待できる
ただし、Pulumiはあくまでプログラミング言語とリソースの操作を結びつけるためのインターフェイスを提供するだけであり、利用するクラウドプラットフォームに関する知識は別途必要だ。また、Pulumiはさまざまなクラウドサービスに対応するが、それぞれのクラウドサービスごとにリソースを作成するために必要な情報は異なるため、複数のクラウドサービスを利用したい場合にはそれぞれのサービスごとに適切な流儀での記述が必要とある。そのため、たとえば単一のコードからAWSでもGCPでもAzureでも同じようにリソースを作成する、といったことが簡単にできるわけではない。
Pulumi社が提供するクラウドサービスとの連携機能も提供される
Pulumiのもう1つの特徴として、Pulumiを開発するPulumi社が提供するクラウドサービス(以下、「Pulumiサービス」と呼ぶ)上で設定や変更履歴などの管理を行える点がある。
Pulumiを使ってクラウド上にリソースを作成したり、それらを変更するような処理を実行すると、その履歴が自動的にPulumiサービスに送信され、Webブラウザ上でそれらの詳細を閲覧できるようになる(ローカルファイルのみで履歴を管理するように設定することも可能)。また、各種設定などもこのクラウド上で管理される。
Pulumiサービスは個人アカウントであれば無料で利用できるほか、チームでの開発・運用に向けた機能も有償で提供しており、これらを利用することでチームで変更履歴を管理できるようになる。ちなみに有償プランの料金は月額50ドルからという設定となっている(Pulumiの「Pricing」ページ)。また、Pulumiサービスでは継続的インテグレーション(CI)や継続的デリバリ(CD)との連携機能も提供しているほか、有償プランではWebフックを使ってクラウドへのリソース作成や変更時に通知などを送信できる機能も提供される。
なお、Pulumi自体はすべてオープンソースで提供されており、独自に履歴管理を行うようなサーバーを作成・運用することもできるという。また、「Enterprise」プランでは公式に履歴管理などのサービスを行うサーバーのセルフホスティング機能を提供している。
Pulumiが対応するクラウドサービス
Pulumiが現時点で公式にサポートしているクラウドサービスとしては下記が挙げられている。
- Amazon Web Services(AWS)
- Microsoft Azure
- Google Cloud
- Kubernetes
また、プラグインで対応するプラットフォームを拡充できる仕組みになっており、さくらのクラウドをPulumiで操作するためのプラグインも公開されている。
Pulumiのインストールと初期設定
それでは、実際にPulumiを使ってクラウド上のリソース管理を行う流れを紹介していこう。
pulumiコマンドのインストール
Pulumiは各種操作を実行するフロントエンドの「pulumi」コマンドと、各種プログラミング言語向けのモジュール(「Cloud Providers」)から構成されており、それぞれのインストールが必要となる。pulumiコマンドのインストール手順は、Pulumi公式サイトの「Download and Install」ページに記載されている。
このページではシェルスクリプトを使ったインストール方法やパッケージマネージャ経由でのインストール方法が説明されているが、ダウンロードページからバイナリを含むアーカイブを直接ダウンロードしても問題ない。なお、本記事では執筆時点での最新版である1.11.1を利用している。
このページで公開されているアーカイブは、LinuxおよびmacOS向けのものはtar.gz形式、Windows向けのものはZIP形式で圧縮されているので、適宜適当なディレクトリに展開する。アーカイブ中にはpulumiコマンドのほか複数のバイナリが含まれているので、これらを適当な(パスの通っている)ディレクトリに移動もしくはコピーすれば良い。
$ wget https://get.pulumi.com/releases/sdk/pulumi-v1.11.1-linux-x64.tar.gz $ tar xvzf pulumi-v1.11.1-linux-x64.tar.gz $ cd pulumi $ ls -1 pulumi pulumi-analyzer-policy pulumi-language-dotnet pulumi-language-go pulumi-language-nodejs pulumi-language-python pulumi-language-python-exec pulumi-resource-pulumi-nodejs pulumi-resource-pulumi-python $ ./pulumi version v1.11.1+dirty
必要なプログラミング言語環境の準備
前述のように、PulumiはTypeScriptもしくはJavaScript、Python、C#、Go言語を使ってリソースの定義を行う。そのため、使用する言語のコンパイラやランタイム環境が必要となる。TypeScriptもしくはJavaScriptを利用する場合はNode.js 8.0以降、PythonではPython 3.6以降、C#では.NET Core 3.0 SDK以降が必要だ。
なお、GoおよびC#はまだプレビュー段階であり公式にはサポートされていない。そのため、Kubernetesなど一部のプラットフォームについては、現時点ではこれら言語との組み合わせでは利用できない。
クラウドプラットフォームごとの事前準備
Pulumiは各クラウドプラットフォームの操作を行う際、それらプラットフォームが提供するフロントエンドを経由してAPIを呼び出す。そのため、事前に使用するクラウドプラットフォームのフロントエンド(Google Cloudの場合「gcloud」コマンド、Microsoft Azureの場合「az」コマンド、AWSの場合「aws」コマンド、Kubernetesの場合「kubectl」コマンド)でクラウドにアクセスできるよう設定を行っておく必要がある(さくらのクラウドの場合は後述する)。
Pulumiサービスへのログイン
Pulumiでは、同社が提供しているクラウドサービス(Pulumiサービス)上で履歴などを管理できるようになっている。この機能を利用するためには、まずPulumiサービスのアカウント作成とログインが必要となる。アカウントの作成は、Pulumiのサービスサイトから行える(図2)。
ここではGitHubやGitLab、Atlassianのアカウントを使っての認証でログインができるほか、メールアドレスを登録してのアカウント作成も可能だ。使用する認証方法を選び、アカウントを作成しておこう。
ローカルマシンからのログイン
pulumiコマンドを使ってクラウドプラットフォームの管理を行うマシン上では、「pulumi login」コマンドを実行することでPulumiサービス上のアカウントとの紐付けが行われ、設定履歴などがそのアカウントに対して記録されるようになる。このとき、トークン文字列の入力が求められるが、これはPulumiサービス上の「https://app.pulumi.com/account/tokens」というURLにアクセスすることで作成できる(図3)。
ここで、「NEW ACCESS TOKEN」をクリックすると作成するトークンの説明を入力するフォームが表示されるので、適当なものを入力して「CREATE」をクリックする(図4)。
するとトークン文字列が表示されるので、これをコピーしておこう(図5)。
pulumiコマンドをインストールしたマシン上で「pulumi login」コマンドを実行すると、次のようにアクセストークンの入力が求められるので、ここでコピーしたトークンを入力して「Enter」を押すとログインが完了する。
$ pulumi login Manage your Pulumi stacks by logging in. Run `pulumi login --help` for alternative login options. Enter your access token from https://app.pulumi.com/account/tokens or hit <ENTER> to log in using your browser : ←トークンを入力する Welcome to Pulumi! Pulumi helps you create, deploy, and manage infrastructure on any cloud using your favorite language. You can get started today with Pulumi at: https://www.pulumi.com/docs/get-started/ Tip of the day: Resources you create with Pulumi are given unique names (a randomly generated suffix) by default. To learn more about auto-naming or customizing resource names see https://www.pulumi.com/docs/intro/concepts/programming-model/#autonaming. Logged into pulumi.com as hylom (https://app.pulumi.com/hylom)
なお、Pulumiサービスで使用するアカウントを切り替えたいといった場合は「pulumi logout」コマンドでログアウトを行える。
$ pulumi logout
なお、Pulumiサービスとの連携をしないことも可能だ。その場合、次のように「--local」オプション付きで「pulumi login」コマンドを実行する。
$ pulumi login --local Logged into centos4x4 as hylom (file://~)
この場合、ホームディレクトリ上に各種ログや設定が保存されるようになる。
KubernetesクラスタをPulumiで操作する
Pulumiではさまざまなクラウドプラットフォームをさまざまな言語で操作できるが、まずは一例として、Kubernetesクラスタ上へのアプリケーションのデプロイをPulumiで行う例を紹介しよう。
プロジェクトとStackの作成
Pulumiでは、「プロジェクト(Project)」および「Stack」という単位でリソースの管理を行うようになっている。プロジェクトはPulumiの設定やリソースを記述したソースコードなどを管理する単位で、またStackは各種設定などを管理する単位だ。たとえば開発向けには「development」、検証向けには「staging」、運用向けには「production」のように異なるStackを作成することで、同じコードベースを元にしつつデプロイ先や設定などを切り替えられるようになっている。
Pulumiを利用するには、最初にプロジェクトを作成する必要がある。プロジェクトの作成は「pulumi new」コマンドを使用する。このコマンドではプロジェクトのひな形を作成するための「テンプレート」を引数として指定するようになっており、使用するクラウドや言語に応じたテンプレートを選択できる。
pulumi new <オプション> <テンプレート>
利用できるテンプレートは、「--help」オプション付きで「pulumi new」コマンドを実行することで確認できる。
$ pulumi new --help Create a new Pulumi project and stack from a template. : : Available Templates: alicloud-csharp A minimal AliCloud C# Pulumi program alicloud-fsharp A minimal AliCloud F# Pulumi program alicloud-go A minimal AliCloud Go Pulumi program alicloud-javascript A minimal AliCloud JavaScript Pulumi program : :
また、テンプレートを指定せずに「pulumi new」コマンドを実行することで、対話的にテンプレートを選択するこもできる。
たとえばPythonを使ってKubernetesクラスタを操作する場合は、テンプレートとして「kubernetes-python」を選択すれば良い。「pulumi new」コマンドの実行時にはプロジェクト名やプロジェクトの説明、stack名(後述)の入力も求められるが、特に入力せずにEnterキーを押すとデフォルトのもの(括弧内に表示されているもの)が使われる。また、デフォルト設定ではプロジェクトの作成と同時に「dev」というStackも作成される。
↓プロジェクトで使用するファイルを管理するディレクトリを作成する $ mkdir test01 $ cd test01 ↓「kubernetes-python」テンプレートからプロジェクトを作成する $ pulumi new kubernetes-python This command will walk you through creating a new Pulumi project. Enter a value or leave blank to accept the (default), and press <ENTER>. Press ^C at any time to quit. ↓プロジェクト名を入力する。デフォルトではディレクトリ名がプロジェクト名となる project name: (test01) ↓プロジェクトの説明を入力する project description: (A minimal Kubernetes Python Pulumi program) Created project 'test01' ↓stack名を入力する Please enter your desired stack name. To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`). stack name: (dev) Created stack 'dev' Your new project is ready to go! To perform an initial deployment, run the following commands: 1. python3 -m venv venv 2. source venv/bin/activate 3. pip3 install -r requirements.txt Then, run 'pulumi up'
なお、ローカルに履歴や設定を残す設定にしている場合、これらに加えて設定やデータなどを暗号化する際に使用するパスフレーズの入力も求められる。
Enter your passphrase to protect config/secrets: ←暗号化に使用するパスフレーズを入力する Re-enter your passphrase to confirm: ←確認のため同じパスフレーズを再度入力する Created stack 'dev' Enter your passphrase to unlock config/secrets (set PULUMI_CONFIG_PASSPHRASE to remember): 入力したパスフレーズを再度入力する
さて、この例のように「pulumi new」コマンドでPython向けのプロジェクトを作成すると、次のように3つのファイルが作成される。
$ ls -1 Pulumi.yaml __main__.py requirements.txt
ここで「Pulumi.yaml」はPulumiの設定などを記述した設定ファイルで、「__main__.py」が作成・管理するリソースを定義するPythonのソースコード、「requirements.txt」がPythonコードで使用する依存モジュールを定義するファイルだ。
Pythonを利用する場合、Pyhon 3.3以降で利用できる「venv」という仕組みを使ってパッケージ管理を行うことが推奨されている。venvはシステム標準とは分離したPython環境をローカルディレクトリ下に作成するもので、これを利用することでシステムとは独立して依存パッケージを管理できるようになる。Pulumiでは多数の依存パッケージを必要とするが、これによってシステム全体に影響を及ぼさずに依存パッケージをインストールできるようになる。
venvを利用するには、次のようにまず「python3 -m venv venv」コマンドを実行してvenvを作成する。
$ python3 -m venv venv
すると、ディレクトリ内に「venv」というディレクトリが作成される。このディレクトリ内の「bin/activate」というスクリプトを実行すると仮想環境内でPythonのモジュールを管理したり、コードを実行できるようになるので、ここで「pip3 install」コマンドを実行して依存パッケージをインストールする。
$ ls -1 Pulumi.yaml __main__.py requirements.txt venv ↓仮想環境を有効にする $ . venv/bin/activate (venv) $ ↓依存関係を記述したrequirements.txtを確認する (venv) $ cat requirements.txt pulumi>=1.0.0 pulumi-kubernetes>=1.0.0 ↓依存パッケージをインストールする (venv) $ pip3 install -r requirements.txt Collecting pulumi>=1.0.0 (from -r requirements.txt (line 1)) Downloading https://files.pythonhosted.org/packages/40/4c/4d1660919a520c684b6af3421865cb9e86ba95474a3c408a4390dac58792/pulumi-1.11.1-py2.py3-none-any.whl (90kB) : : Running setup.py install for dill ... done Running setup.py install for pulumi-kubernetes ... done Successfully installed arpeggio-1.9.2 attrs-19.3.0 certifi-2019.11.28 chardet-3.0.4 dill-0.3.1.1 grpcio-1.27.2 idna-2.8 parver-0.3.0 protobuf-3.11.3 pulumi-1.11.1 pulumi-kubernetes-1.5.6 requests-2.21.0 semver-2.9.1 six-1.14.0 urllib3-1.24.3
なお、筆者が試した環境では別途PulumiのKubernetesプラグインのインストールも必要だった。これは、PulumiのKubernetesプラグインがインストールされていない、もしくは最新版ではない場合に発生するようで、その際は後述する「pulumi up」コマンドの実行時などに次のようなメッセージが表示される
Diagnostics: pulumi:providers:kubernetes (default_1_5_7): error: no resource plugin 'kubernetes-v1.5.7' found in the workspace or on your $PATH, install the plugin using `pulumi plugin install resource kubernetes v1.5.7`
この場合、指示に従って「pulumi plugin install」コマンドを実行すれば良い。
(venv) $ pulumi plugin install resource kubernetes v1.5.7 [resource plugin kubernetes-1.5.7] installing Downloading plugin: 19.93 MiB / 19.93 MiB [=========================] 100.00% 3s Moving plugin... done.
次に、「pulumi new」コマンドで作成されたソースコードを編集して作成したいリソースを記述する。今回のようにkubernetes-pythonテンプレートからプロジェクトを作成した場合、__main__.pyには次のようなnginxをデプロイするコードが記述されているはずだ。
import pulumi from pulumi_kubernetes.apps.v1 import Deployment app_labels = { "app": "nginx" } deployment = Deployment( "nginx", spec={ "selector": { "match_labels": app_labels }, "replicas": 1, "template": { "metadata": { "labels": app_labels }, "spec": { "containers": [{ "name": "nginx", "image": "nginx" }] } } }) pulumi.export("name", deployment.metadata["name"])
「pulumi up」コマンドを実行すると、ここで定義されたリソースが実際に作成される。
(venv) $ pulumi up Previewing update (dev): Type Name Plan + pulumi:pulumi:Stack test01-dev create + mq kubernetes:apps:Deployment nginx create Resources: + 2 to create Do you want to perform this update? yes Updating (dev): Type Name Status + pulumi:pulumi:Stack test01-dev created + mq kubernetes:apps:Deployment nginx created Outputs: name: "nginx-j9slxlsv" Resources: + 2 created Duration: 16s Permalink: https://app.pulumi.com/hylom/test01/dev/updates/1
なお、「pulumi up」コマンドの実行時には次のように確認が求められる。ここでは矢印キーでカーソルを動かせるので、問題がなければカーソルを「yes」に動かしてEnterキーを押せば良い。
Do you want to perform this update? > yes no details
また、ローカルに履歴を保存するように設定している場合は、ここでアンロックのためのパスフレーズの入力を求められる。
Enter your passphrase to unlock config/secrets (set PULUMI_CONFIG_PASSPHRASE to remember):
毎回このパスフレーズを入力するのが面倒な場合は、パスフレーズを「PULUMI_CONFIG_PASSPHRASE」環境変数に格納しておけば良い。そうすると、この環境変数を参照して自動的に認証が行われるようになる。
最後に表示されているURLはPulumiサービス上でこの操作の詳細情報を確認するためのものだ。このURLにアクセスすることで、Webブラウザで実行された処理を確認できる(図6)。
ちなみにローカルに履歴を保存していた場合、このURLはローカルファイルへのパスになる。
Permalink: file:///home/hylom/.pulumi/stacks/dev.json
Stack情報の確認と削除
Pulumiで作成したリソースは、Pulumi上ではStack単位で管理され、その情報は「pulumi stack」コマンドで確認できる。
$ pulumi stack Current stack is dev: Owner: hylom Last updated: 5 minutes ago (2020-03-10 17:33:49.444034418 +0900 JST) Pulumi version: v1.11.1+dirty Current stack resources (3): TYPE NAME pulumi:pulumi:Stack test01-dev tq kubernetes:apps/v1:Deployment nginx mq pulumi:providers:kubernetes default_1_5_6 Current stack outputs (1): OUTPUT VALUE name nginx-j9slxlsv More information at: https://app.pulumi.com/hylom/test01/dev Use `pulumi stack select` to change stack; `pulumi stack ls` lists known ones
また、Pulumiサービスからも、作成したStackの情報や履歴(activity)、作成されたリソースといった情報を確認できる(図7、8)。
このときkubectlコマンドで作成されたリソースを確認すると、次のように実際に対応するdeploymentが作成されていることが分かる。
(venv) $ kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE nfs-client-provisioner 1/1 1 1 32d nginx-j9slxlsv 1/1 1 1 6m2s (venv) $ kubectl get pod NAME READY STATUS RESTARTS AGE nfs-client-provisioner-78b7d65564-kkzw4 1/1 Running 0 32d nginx-j9slxlsv-554b9c67f9-qb4kp 1/1 Running 0 6m5s
また、リソースの作成や削除といった操作は、基本的にはすべて履歴として記録されており、「pulumi history」コマンドで確認できる。
(venv) $ pulumi history $ pulumi history UpdateKind: import Status: succeeded Message: +0-1~0 0 Updated 3 minutes ago took 0s UpdateKind: import Status: succeeded Message: +0-1~0 1 Updated 4 minutes ago took 0s UpdateKind: import Status: succeeded Message: +0-1~0 2 Updated 8 minutes ago took 0s UpdateKind: update Status: succeeded Message: +2-0~0 0 Updated 18 minutes ago took 16s
この履歴はWebブラウザでPulumiサービスにアクセスすることでも確認できる(図9)。
Stackとそれに関連付けられているリソースを削除するには「pulumi destroy」コマンドを使用する。「pulumi up」コマンドで作成されたリソースが存在している場合はそれらリソースは同時に削除される。コマンドの実行時には「pulumi up」コマンドの実行時と同様に確認が求められるので、「yes」を選択する。
(venv) $ pulumi destroy Previewing destroy (dev): Resources: Do you want to perform this destroy? yes ←「yes」と入力する Destroying (dev): Permalink: https://app.pulumi.com/hylom/test01/dev/updates/5 The resources in the stack have been deleted, but the history and configuration associated with the stack are still maintained. If you want to remove the stack completely, run 'pulumi stack rm dev'.
なお、Stackを削除した場合でも、その履歴については削除されず残される。履歴までも含めてStackを削除したい場合は、「pulumi stack rm <スタック名>」コマンドを実行する。
(venv) $ pulumi stack rm dev This will permanently remove the 'dev' stack! Please confirm that this is what you'd like to do by typing ("dev"): dev ←確認のためStack名の入力を求められる Stack 'dev' has been removed!
Stackを削除すると、同時にPulumiサービス上での履歴なども削除される。なお、稼働しているリソースが存在しない場合は、PulumiサービスからStackを削除することもできる(図10)。
JavaScriptでのリソース管理
ここまでではPythonでリソースを記述していたが、JavaScript(もしくはTypeScript)でリソースを記述する場合も作業の流れはほぼ同じだ。その場合、「pulumi new」コマンドで指定するテンプレートとして「kubernetes-javascript」(JavaScriptの場合)もしくは「kubernetes-typescript」(TypeScriptの場合)を選択すれば良い。
$ pulumi new kubernetes-javascript This command will walk you through creating a new Pulumi project. Enter a value or leave blank to accept the (default), and press <ENTER>. Press ^C at any time to quit. project name: (test02) project description: (A minimal Kubernetes JavaScript Pulumi program) Created project 'test02' Please enter your desired stack name. To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`). stack name: (dev) Created stack 'dev' Installing dependencies... : : Finished installing dependencies Your new project is ready to go! To perform an initial deployment, run 'pulumi up'
これで、JavaScript(もしくはTypeScript)でのリソース管理に必要なファイル一式が作成される。
$ ls -1 Pulumi.yaml index.js node_modules package-lock.json package.json
ここでは「index.js」がリソースを定義するソースコードファイルとなっており、次のような内容になっている。
$ cat index.js "use strict"; const k8s = require("@pulumi/kubernetes"); const appLabels = { app: "nginx" }; const deployment = new k8s.apps.v1.Deployment("nginx", { spec: { selector: { matchLabels: appLabels }, replicas: 1, template: { metadata: { labels: appLabels }, spec: { containers: [{ name: "nginx", image: "nginx" }] } } } }); exports.name = deployment.metadata.name;
Pythonの場合はpip3コマンドで依存するモジュールのインストールを実行していたが、JavaScriptの場合は自動的にその処理が実行されるので、そのまま「pulumi up」コマンドを実行するとリソースの作成が行われる。
$ pulumi up Previewing update (dev): Type Name Plan + pulumi:pulumi:Stack test02-dev create + mq kubernetes:apps:Deployment nginx create Resources: + 2 to create Do you want to perform this update? yes Updating (dev): Type Name Status + pulumi:pulumi:Stack test02-dev created + mq kubernetes:apps:Deployment nginx created Outputs: name: "nginx-0jtwanyd" Resources: + 2 created Duration: 13s Permalink: https://app.pulumi.com/hylom/test02/dev/updates/1
独自のリソース定義を行う
続いては、「pulumi new」コマンドで作成された設定ファイルを修正して、作成するリソースを独自に定義してみよう。今回作成するのは、WordPressを実行するPodと、WordPressが使用するデータベース(MariaDB)を実行するPodの組み合わせという環境だ。具体的には、次のような構成となる。
- WordPressはDockerHubで公開されている「wordpress」イメージを使用する
- MariaDBは同じくDockerHubで公開されている「mariadb」イメージを使用する
- MariaDBのストレージはPersistent Volume Claimを使用して確保する
- MariaDBへのアクセスに使用するパスワードはKubernetesのSecretを使用して管理する
- Pulumiでのリソースの定義にはPythonを利用する
なお、データベースアクセスに使用するパスワードは、次のように「kubectl create secret」コマンドでSecretリソースを作成して格納しておく。
$ kubectl create secret generic mariadb-cred --from-literal=MYSQL_ROOT_PASSWORD=<MariaDBのルートパスワード> --from-literal=MYSQL_PASSWORD=<WordPressで使用するデータベースへのアクセスに使用するパスワード>
ここで作成したSecretリソースは、次のようにして確認できる。
$ kubectl describe secret mariadb-cred Name: mariadb-cred Namespace: default Labels: <none> Annotations: <none> Type: Opaque Data ==== MYSQL_ROOT_PASSWORD: 6 bytes MYSQL_PASSWORD: 8 bytes
Pythonによるリソース定義
さて、それではプロジェクトを作成し、実際にリソース定義を行っていこう。今回はPythonを使用するので、テンプレートとして「kubernetes-python」を選択してプロジェクトを作成し、pyenvやpip3コマンドによる依存モジュールのインストールを行っておく。
$ mkdir wp_py $ cd wp_py/ $ pulumi new kubernetes-python $ python3 -m venv venv $ . venv/bin/activate (venv) $ pip3 install -r requirements.txt
続いて、メインのソースコードである__main__.pyを編集し、リソースの定義を行っていく。
モジュールのインポート
リソースの定義は、作成したいリソースに対応するクラスをインポートし、そのコンストラクタを実行してオブジェクトを作成することで行う。たとえばKubernetes関連のリソースは「pulumi_kubernetes.<グループ>.<バージョン>」という名前空間内で定義されているので、ここからクラスをインポートすることになる。ここでの「グループ」および「バージョン」はマニフェストファイルの「apiVersion」などでも使用される、Kubernetes APIのグループおよびバージョンに相当する。たとえば、「v1 core」APIで定義されているServiceリソースに対応するクラスは「pulumi_kubernetes.core.v1」という名前空間で定義されており、また「v1 apps」APIで定義されているDeploymentリソースに対応するクラスは「pulumi_kubernetes.apps.v1」という名前空間内で定義されている。
今回は「v1 core」APIに含まれるPersistentVolumeClaimリソースおよびServiceリソースと、「v1 apps」APIに含まれるDeploymentリソースおよびStatefulSetリソースを使用するので、次のようにimport文を記述する。
import pulumi from pulumi_kubernetes.core.v1 import PersistentVolumeClaim, Service from pulumi_kubernetes.apps.v1 import Deployment, StatefulSet
なお、モジュールについての詳細は公式ドキュメントの「API Reference」ページに記載されている。ここでは対象とするクラウドプロバイダおよび使用する言語ごとにAPIがまとめられており、たとえばKubernetesとPythonという組み合わせであればPulumi Kubernetesというページから各モジュールのドキュメントを参照できる。
インスタンスの作成
続いて、ソースコード内でこれらクラスのコンストラクタを実行してインスタンスを作成することで、作成するリソースを定義する。Kubernetesの場合、リソースに対応するクラスの多くは次のような引数を取る。
<クラス名>(<リソース名>, opts=<オプション>, metadata=<メタデータ>, spec=<Spec>)
ここで、「オプション(opts)」はPulumiがそのリソースを扱う際の振る舞いを指定するパラメータだ。基本的には特に指定する必要はないが、依存関係などを厳密に定義したい場合はこのパラメータを指定する(ドキュメント)。「メタデータ(metadata)」はそのリソースに与えるメタデータ、「Spec」はそのリソースの詳細情報を指定するもので、それぞれKubernetesのManifestファイルでの「metadata:」や「spec:」要素に相当する。
たとえば、MariaDBで使用する3306番ポートを使用するServiceの定義は次のようになる。ここでは「metadata」や「selector」の指定でmariadb_app_name変数を使っているが、このように自由に変数を利用できる点もPulumiを使ったリソース定義のメリットの1つとなっている。
# リソース名を変数で定義する mariadb_app_name = "mariadb-wordpress" # Serviceクラスのインスタンスを作成する svc = Service(mariadb_app_name, metadata={ "name": mariadb_app_name, }, spec={ "selector": { "app": mariadb_app_name }, "ports": [ { "protocol": "TCP", "port": 3306, "targetPort": 3306, }, ], })
ちなみにこれをManifestファイルで記述すると次のようになる。これと比較すると分かるように、今回のコードではManifestで指定しているmetadataやspecを引数で与えている。
apiVersion: v1 kind: Service metadata: name: mariadb-wordpress spec: selector: app: mariadb-wordpress ports: - protocol: TCP port: 3306 targetPort: 3306
manifest引数やspec引数で指定する値はPythonの辞書型データそのものであり、別の方法でこれらを構築することもできる。たとえば、次のようにYAML形式で記述したものを変換して与える、といったことも可能だ。
imort yaml # YAML形式でManifestを記述する wp_svc_yaml = ''' apiVersion: v1 kind: Service metadata: name: wordpress spec: selector: app: wordpress ports: - protocol: TCP port: 80 targetPort: 80 ''' # YAML形式を辞書型データに変換する wp_svc_manifest = yaml.safe_load(wp_svc_yaml) # Serviceクラスのインスタンスを作成する wp_svc = Service("wordpress", metadata=wp_svc_manifest["metadata"], spec=wp_svc_manifest["spec"])
Pythonでは標準ではYAMLをサポートしていないため、ここでは別途「pyyaml」というモジュールをインストールしておく必要がある。これは、requirement.txtに次のように「pyyaml>=5.3」という記述を追加した上で「pip3 install -r requirement.txt」コマンドを実行すれば良い。
pulumi>=1.0.0 pulumi-kubernetes>=1.0.0 pyyaml>=5.3
同様にして必要なすべてのリソースを定義したものが、次のソースコードになる。
import yaml import pulumi from pulumi_kubernetes.core.v1 import PersistentVolumeClaim, Service from pulumi_kubernetes.apps.v1 import Deployment, StatefulSet mariadb_app_name = "mariadb-wordpress" pvc_name = "mariadb-pvc" mariadb_ver = "10.4" # MariaDB用のService定義 svc = Service(mariadb_app_name, metadata={ "name": mariadb_app_name, }, spec={ "selector": { "app": mariadb_app_name }, "ports": [ { "protocol": "TCP", "port": 3306, "targetPort": 3306, }, ], }) # MariaDB用のPersistentVolumeClaim定義 pvc = PersistentVolumeClaim("mariadb-pvc", metadata={ "name": pvc_name, "annotations": { "volume.beta.kubernetes.io/storage-class": "managed-nfs-storage", }, }, spec={ "accessModes": ["ReadWriteMany",], "resources": { "requests": { "storage": "1Gi", }, }, }) # MariaDBのPodを管理するためのStatefulSet定義 ss = StatefulSet(mariadb_app_name, metadata={ "name": mariadb_app_name }, spec={ "selector": { "matchLabels": { "app": mariadb_app_name, }, }, "serviceName": mariadb_app_name, "replicas": 1, "template": { "metadata": { "labels": { "app": mariadb_app_name, }, }, "spec": { "containers": [{ "name": mariadb_app_name, "image": "mariadb:" + mariadb_ver, "volumeMounts": [ { "name": "nfs-pvc", "mountPath": "/var/lib/mysql", }, ], "envFrom": [ { "secretRef": { "name": "mariadb-cred" } }, ], "env": [ { "name": "MYSQL_USER", "value": "wordpress" }, { "name": "MYSQL_DATABASE", "value": "wordpress" }, ], }], "volumes": [{ "name": "nfs-pvc", "persistentVolumeClaim": { "claimName": pvc_name }, }], }, }, }) # WordPressのPodを管理するためのDeployment定義 wp_deploy_yaml = ''' apiVersion: apps/v1 kind: Deployment metadata: name: wordpress-deployment labels: app: wordpress spec: replicas: 1 selector: matchLabels: app: wordpress template: metadata: labels: app: wordpress spec: containers: - name: wordpress image: wordpress:5.3-php7.3 ports: - containerPort: 80 env: - name: WORDPRESS_DB_HOST value: mariadb-wordpress - name: WORDPRESS_DB_USER value: wordpress - name: WORDPRESS_DB_NAME value: wordpress - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: mariadb-cred key: MYSQL_PASSWORD ''' wp_deploy_manifest = yaml.safe_load(wp_deploy_yaml) wp_deploy = Deployment("wordpress-deployment", metadata=wp_deploy_manifest["metadata"], spec=wp_deploy_manifest["spec"]) # WordPressで使用するServiceの定義 wp_svc_yaml = ''' apiVersion: v1 kind: Service metadata: name: wordpress spec: selector: app: wordpress ports: - protocol: TCP port: 80 targetPort: 80 ''' wp_svc_manifest = yaml.safe_load(wp_svc_yaml) wp_svc = Service("wordpress", metadata=wp_svc_manifest["metadata"], spec=wp_svc_manifest["spec"]) # 出力ログに表示する内容の指定 pulumi.export("name", wp_deploy.metadata["name"])
最後に実行している「pulumi.export()」メソッドは、「pulumi up」コマンドの実行結果に任意の値を出力するためのものだ。引数として「<名前>, <値>」の組み合わせを与えてこのメソッドを実行することで、コマンド実行時の「Outputs:」以下にここで指定した名前と値の組み合わせが表示される。たとえば今回の例では、「name: <Deploymentリソースのmetadataで指定された名前>」という文字列が表示されるようになる。
さて、このソースコードを作成した後に「pulumi up」コマンドを実行すると、次のようにリソースが作成される。
$ pulumi up Previewing update (dev): Type Name Plan + pulumi:pulumi:Stack wp_py-dev create + tq kubernetes:core:PersistentVolumeClaim mariadb-pvc create + tq kubernetes:core:Service mariadb-wordpress create + tq kubernetes:core:Service wordpress create + tq kubernetes:apps:StatefulSet mariadb-wordpress create + mq kubernetes:apps:Deployment wordpress-deployment create Resources: + 6 to create Do you want to perform this update? yes Updating (dev): Type Name Status + pulumi:pulumi:Stack wp_py-dev created + tq kubernetes:core:PersistentVolumeClaim mariadb-pvc created + tq kubernetes:core:Service wordpress created + tq kubernetes:core:Service mariadb-wordpress created + tq kubernetes:apps:StatefulSet mariadb-wordpress created + mq kubernetes:apps:Deployment wordpress-deployment created Outputs: name: "wordpress-deployment" Resources: + 6 created Duration: 19s Permalink: https://app.pulumi.com/hylom/wp_py/dev/updates/1
なお、ソースコード中に文法エラーなどがある場合はリソースの作成前にエラーが発生する。また、リソースの作成/変更前には必ず確認も行われる。意図せずリソースが作成されないよう、そこで表示される内容をチェックしておこう。
リソースの更新
リソース定義を変更し、それを実環境に反映させる場合も「pulumi up」コマンドを使用する。たとえば、WordPressを実行させるPodのレプリカ数を次のように「1」から「2」に変更し、再度「pulumi up」コマンドを実行してみよう。
wp_deploy_yaml = ''' apiVersion: apps/v1 kind: Deployment metadata: name: wordpress-deployment labels: app: wordpress spec: replicas: 2
すると、次のように変更が行われたリソースが検出され、そのリソースだけが実際に変更される。
(venv) $ pulumi up Previewing update (dev): Type Name Plan Info pulumi:pulumi:Stack wp_py-dev ~ mq kubernetes:apps:Deployment wordpress-deployment update [diff: ~spec] Resources: ~ 1 to update 5 unchanged Do you want to perform this update? yes Updating (dev): Type Name Status Info pulumi:pulumi:Stack wp_py-dev ~ mq kubernetes:apps:Deployment wordpress-deployment updated [diff: ~spec] Outputs: name: "wordpress-deployment" Resources: ~ 1 updated 5 unchanged Duration: 9s Permalink: https://app.pulumi.com/hylom/wp_py/dev/updates/2
Pulumiによるさくらのクラウドの管理
PulumiではKubernetesだけでなく、さまざまなクラウドサービスを同じような手順で管理できるのが特徴だ。そこで続いては、Pulumiを使ってさくらのクラウド上のリソースを管理する方法を紹介しよう。
Pulumiは公式にはさくらのクラウドをサポートしていないが、コミュニティによってPulumiからさくらのクラウドを操作するためのプラグイン(pulumi-sakuracloud)が開発されており、こちらを導入することでPulumiからさくらのクラウドのリソースを作成したり、それらを操作することが可能になる。
プラグインのインストール
プラグインのインストールは、次のように「pulumi plugin install」コマンドで行える。
$ pulumi plugin install resource sakuracloud 0.1.0 --server https://github.com/sacloud/pulumi-sakuracloud/releases/download/0.1.0
このコマンドを実行すると、プラグインのダウンロードとインストールが実行される。なお、pulumi-sakuracloudではほかのPulumiがサポートしているクラウドインフラと同様、JavaScriptおよびTypeScript、Python、Go、.NET coreでリソースを定義できる。
プロジェクトの作成
続いてはプロジェクトの作成だが、pulumi-sakuracloudのリポジトリ上でテンプレートが公開されており、こちらを利用してプロジェクトを作成できるようになっている(表1)。これらのURLから使用する言語に対応するものを選び、それを引数として「pulumi new」コマンドを実行すれば良い。
言語 | URL |
---|---|
JavaScript | https://github.com/sacloud/pulumi-sakuracloud/tree/master/templates/javascript |
Python | https://github.com/sacloud/pulumi-sakuracloud/tree/master/templates/python |
TypeScript | https://github.com/sacloud/pulumi-sakuracloud/tree/master/templates/typescript |
たとえばJavaScriptを使用する場合、次のように実行する。
$ pulumi new https://github.com/sacloud/pulumi-sakuracloud/tree/master/templates/javascript
クラウドを指定しない「javascript」テンプレートを指定してプロジェクトを作成し、その後さくらのクラウド向けのモジュール(「@sacloud/pulumi_sakuracloud」)を手動でインストールしても良い。
$ pulumi new javascript $ npm install @sacloud/pulumi_sakuracloud
なお、Pythonの場合も基本的な手順は同じだ。手動でモジュールをインストールする場合は、pip3コマンドで「pulumi_sakuracloud」モジュールをインストールすれば良い。
トークンの登録
さくらのクラウドでは、アクセストークンおよびアクセストークンシークレットを使ってユーザー認証を行う仕組みになっている。これらはさくらのクラウドのコントロールパネルの「API Key」画面から作成できる(図11)
pulumi-sakuracloudでは、このアクセストークンおよびアクセストークンシークレットを環境変数もしくはPulumiの設定管理機能(「pulumi config」コマンド)経由で指定する。環境変数で指定する場合、次のように「SAKURACLOUD_ACCESS_TOKEN」および「SAKURACLOUD_ACCESS_TOKEN_SECRET」、「SAKURACLOUD_ZONE」という3つの環境変数にそれぞれの文字列を格納しておく。
$ export SAKURACLOUD_ACCESS_TOKEN=<アクセストークン> $ export SAKURACLOUD_ACCESS_TOKEN_SECRET=<アクセストークンシークレット> $ export SAKURACLOUD_ZONE=<ゾーンID>
なお、ゾーンIDについては表2のものを指定する(「さくらのクラウドAPI1.1」ページ)。
ゾーン | ゾーンID |
---|---|
東京第1 | tk1a |
石狩第1 | is1a |
石狩第2 | is1b |
Sandbox | tk1v |
Pulumiの設定管理機能を利用する場合は、次のように「pulumi config set」コマンドを使って「sakuracloud:token」および「sakuracloud:secret」、「sakuracloud:zone」というキーに対しそれぞれの文字列を指定する。
$ pulumi config set --secret sakuracloud:token <アクセストークン> $ pulumi config set --secret sakuracloud:secret <アクセストークンシークレット> $ pulumi config set --secret sakuracloud:zone <ゾーンID>
リソースを記述する
以上でさくらのクラウドをPulumi経由で操作するための準備は完了だ。続いて、作成するリソースの定義を行っていこう。基本的には次のような形式でリソースを宣言できる。
<クラス名>(<リソース名>, <パラメータ>)
ここでリソース名はそのリソースを示す文字列で、またパラメータはリソース作成に必要な情報を辞書型で指定するものだ。指定するパラメータは「Terraform for さくらのクラウド」のドキュメントにまとめられているものと同じで、「必須」となっているものは必ず指定しなければならない。各種IDはAPI経由で取得できるほか、さくらのクラウドコントロールパネルなどからも参照できる。
たとえばCentOSのアーカイブからディスクを作成し、そのディスクと追加で作成したNICを搭載したサーバーを作成するコードは次のようになる。
"use strict"; const pulumi = require("@pulumi/pulumi"); const sakuracloud = require("@sacloud/pulumi_sakuracloud"); const archiveId = 113200099576; // CentOSの 8.1のアーカイブに対応するID const switchId = 112700601664; // 接続するスイッチのID // Diskリソースを作成する const disk = new sakuracloud.Disk("pulumi-test-disk", { name: "pulumi-test-disk", plan: "ssd", source_archive_id: archiveId, }); // 作成したdiskリソースを接続したServerリソースを作成 const server = new sakuracloud.Server("pulumi-test", { name: "pulumi-test", disks: [ disk.id, ], core: 1, memory: 1, nic: "shared", additional_nics: [ switchId, ], description: "for pulumi test", hostname: "pulumi01", ssh_key_ids: [], disable_pw_auth: false, }); // 作成したServerおよびDiskのIDを出力する module.exports.serverId = server.id; module.exports.diskId = disk.id;
ここで、Diskリソースのコピー元となるアーカイブのIDはさくらのクラウドのコントロールパネルで確認できる(図12)。
また、ここでは標準の(共有ネットワークに接続された)NICに加えて、指定したスイッチに接続するNICを追加している。このIDも同様にコントロールパネルから確認可能だ(図13)。
リソースの記述完了後「pulumi up」コマンドを実行すると、定義したリソースが作成される。
$ pulumi up Previewing update (dev): Type Name Plan + pulumi:pulumi:Stack sakura-js-dev create + tq sakuracloud:index:Disk pulumi-test-disk create + mq sakuracloud:index:Server pulumi-test create Resources: + 3 to create Do you want to perform this update? yes Updating (dev): Type Name Status + pulumi:pulumi:Stack sakura-js-dev created + tq sakuracloud:index:Disk pulumi-test-disk created + mq sakuracloud:index:Server pulumi-test created Outputs: diskId : "113200417849" serverId: "113200417850" Resources: + 3 created Duration: 47s Permalink: https://app.pulumi.com/hylom/sakura-js/dev/updates/1
ここで作成されたリソースは、Kubernetesの場合と同じように「pulumi destroy」コマンドで削除できる。
さまざまなクラウドを統一的に操作できる点が最大の利点
このように、Pulumiはプログラミング言語を使って作成・管理するリソースを定義できる点と、Pulumiサービスを使った管理機能が特徴となる。PythonやJavaScriptなどをすでに習得していれば、ツール独自の設定ファイルの書式を学習しなくて良いという点はメリットだろう。また、異なるクラウドプラットフォームでも基本的には同じような流れで操作が行えるため、複数のクラウドプラットフォームにまたがって管理を行っているようなケースでは特に有用だ。
一方の管理機能については、無料プランではチーム機能が利用できないこともあり、あまりメリットは感じられない。とはいえ、複数人でインフラ管理を行うようなケースにおいては活用できる可能性があるので、費用対効果を考えたうえで検討すると良いだろう。