目次
本当のInfrastructure as Codeを実現するPulumi
インフラをコードにして管理する、所謂Infrastructure as Code(IaC)、特にクラウド上のインフラ管理はTerraformというツールがよく使われていると思います。クラウドエースでもインフラ管理にはTerraformを使用しています。しかし、このInfrastructure as Codeは本当のCodeではなく、Configurationです。今回は本当のInfrastructure as Codeを実践するPulumiというツールを紹介したいと思います。
Pulumiはまさにプログラミングでインフラを自動化する
Pulumiは自分が好きなプログラミング言語を用いてインフラを管理するツールです。今の段階でサポートしている言語はJavaScript、TypeScript、Python、Goで、今後は他の言語も対応する予定です。そしてサポートしているインフラは、Amazon Web Services、Microsoft Azure、Google Cloud Platform のほか、KubernetesやOpenStackもサポートしています。
早速ですが、Pulumiを試してみましょう。
今回の目標はGCPにNetworkを作って、その中にSubnetを作ります。そして、とあるRegionにあるすべてのZoneにそれぞれのSubnetでVMを作ることです。
pulumiインストール
LinuxやMacOSの場合
curl -fsSL https://get.pulumi.com | sh
MacOSでは brew でも使えます
brew install pulumi
正常にインストールできたか確認
pulumi version
v0.16.16
Command not foundの場合は、
export PATH=$HOME/.pulumi/bin:$PATH
pulumiログイン
デフォルトはpulumiが使うstateはPulumi Cloudに保存するので、Pulumi Cloudでアカウントを作ってログインする必要があります。stateをローカルのファイルシステムに保存して使うこともできるので、今回はローカルを使います。
pulumi login --local
GCP credentials取得
GCP consoleにログインして、https://console.cloud.google.com/apis/credentials にてcredentialsを取得します。
service account key を選択します。
新しいservice accountを作ります。
そして、JSON鍵ファイルをダウンロードします。
設定
export GOOGLE_CREDENTIALS=$(cat credentials.json)
nodejsインストール
今回はtypescriptを使うので、nodejsをインストールする必要があります。既にインストールされている場合は不要です。具体的なインストール方法は https://nodejs.org/en/download/package-manager/ を参照ください。
これで準備は万端。これからpulumiにてプロジェクトを作ります。
Pulumiプロジェクトを作る
Pulumiプロジェクトを作るには new を使います。
pulumi new gcp-typescript --dir gcp-vm
gcp-typescript を使って、gcp-vm というディレクトリが生成されます。いろいろ聞かれると思いますが、デフォルトのままで結構です。
gcp:project: The Google Cloud project to deploy into: と聞かれたら、自分のGCPプロジェクトの名前を入れてください。
最後
Do you want to perform this update?
yes
> no
details
noで大丈夫です。
ソースコード書く
上のステップで gcp-vm というディレクトリが生成されます。中には index.ts があります。このファイルにコードを追加します。
Network作成
const baseName = 'cloudace-pulumi-demo';
const network = new gcp.compute.Network(baseName, {
name: baseName,
autoCreateSubnetworks: false,
});
Subnet作成
//DemoのためSubnet作成の関数を作ります。
const createSubnet = function (networkId: pulumi.Output<string>, subnetName: string, cidr: string, region: string): gcp.compute.Subnetwork {
let subnet = new gcp.compute.Subnetwork(subnetName, {
name: subnetName,
network: networkId,
region: region,
ipCidrRange: `${cidr}`,
enableFlowLogs: true,
});
return subnet;
}
const subnets = ['10.0.20.0/24', '10.0.21.0/24', '10.0.22.0/24'].map((cidr, i) => {
return createSubnet(network.id, `${baseName}-${i}`, cidr, region);
});
Zone取得
const zoneResult = gcp.compute.getZones({region});
VM作成
// loopなども使えます。
for (let i = 0; i < 10; i++) {
const zone = zoneResult.then(z => getArrayElement(z.names, i));
const subnet = getArrayElement(subnets, i).selfLink;
new gcp.compute.Instance(`${baseName}-${i}`, {
machineType: machineType,
zone: zone,
bootDisk: {
initializeParams: {
image: image,
},
},
networkInterfaces: [{
subnetwork: subnet,
accessConfigs: [{}],
}],
});
}
Pulumi preview
Previewing update (dev):
Type Name Plan
+ pulumi:pulumi:Stack gcp-vm-dev create
+ ├─ gcp:compute:Network cloudace-pulumi-dev create
+ ├─ gcp:compute:Subnetwork cloudace-pulumi-dev-1 create
+ ├─ gcp:compute:Subnetwork cloudace-pulumi-dev-2 create
+ ├─ gcp:compute:Subnetwork cloudace-pulumi-dev-0 create
+ ├─ gcp:compute:Instance cloudace-pulumi-dev-0 create
+ ├─ gcp:compute:Instance cloudace-pulumi-dev-2 create
+ └─ gcp:compute:Instance cloudace-pulumi-dev-1 create
Resources:
+ 8 to create
どういうものが作られるかを事前に確認できます。問題なければ、実行します。
実行
pulumi up
Updating (dev):
Type Name Status
+ pulumi:pulumi:Stack gcp-vm-dev creating...
+ ├─ gcp:compute:Network cloudace-pulumi-dev created
+ ├─ gcp:compute:Subnetwork cloudace-pulumi-dev-1 creating...
+ ├─ gcp:compute:Subnetwork cloudace-pulumi-dev-2 creating...
+ └─ gcp:compute:Subnetwork cloudace-pulumi-dev-0 creating...
リアルタイムで状態を確認できます。
削除
リソースが不要になった場合は以下のコマンドで削除できます。
pulumi destroy
index.ts全貌
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
/**
* Configuration、外部のconfigファイルに保存することもできます
* https://pulumi.io/reference/config.html.
*/
const baseName = `cloudace-pulumi-${pulumi.getStack()}`;
const region = "asia-northeast1";
const subnetCidrs = ['10.0.20.0/24', '10.0.21.0/24', '10.0.22.0/24'];
const numberOfVirtualMachines = 3;
const machineType = 'f1-micro';
const image = 'debian-cloud/debian-9-stretch-v20181210';
/**
* Subnetを作る関数
*/
const createSubnet = function (networkId: pulumi.Output<string>, subnetName: string, cidr: string, region: string): gcp.compute.Subnetwork {
let subnet = new gcp.compute.Subnetwork(subnetName, {
name: subnetName,
network: networkId,
region: region,
ipCidrRange: `${cidr}`,
enableFlowLogs: true,
});
return subnet;
}
const getArrayElement = function <T>(values: T[], index: number): T {
return values[Math.abs(index) % values.length];
}
/**
* Networkを作る
*/
const network = new gcp.compute.Network(baseName, {
name: baseName,
autoCreateSubnetworks: false,
});
/**
* Subnetを作る
*/
const subnets = subnetCidrs.map((cidr, i) => {
return createSubnet(network.id, `${baseName}-${i}`, cidr, region);
});
/**
* zone取得
*/
const zoneResult = gcp.compute.getZones({region});
/**
* VMを作る
*/
const virtualMachines = [];
for (let i = 0; i < numberOfVirtualMachines; i++) {
const zone = zoneResult.then(z => getArrayElement(z.names, i));
const subnet = getArrayElement(subnets, i).selfLink;
const virtualMachine = new gcp.compute.Instance(`${baseName}-${i}`, {
machineType: machineType,
zone: zone,
bootDisk: {
initializeParams: {
image: image,
},
},
networkInterfaces: [{
subnetwork: subnet,
accessConfigs: [{}],
}],
});
virtualMachines.push(virtualMachine);
}
export const networkId = network.id;
export const virtualMachineNames = virtualMachines.map(vm => vm.name);
いかがだったでしょうか。
Terraformとは違い、まさにプログラムからInfrastructureを管理することができます。
一見、各クラウドのAPIをただ呼び出しているだけのように見えますが、実際はPulumiで管理された状態ファイルを確認しながら差分の更新が行えるようになっています。
今回は紹介できませんでしたが、AWS LambdaやGoogle Cloud Functions などもPulumiで書くことができます。上記のコードと組み合わせることで、LambdaやGCFのエンドポイントを叩くことでインフラ構築が自動で行われる、なんてこともできちゃいます。
(これはまた別の機会にご紹介します)
それではみなさんも是非試してみてください!!
クラウドエースでは最先端の技術を的確に扱うことができるエンジニア、ともにそこを目指すエンジニアを募集しています!!