クラウド同上

まだTerraform使ってるの?未来はPulumiだよ

Author
Lv:2 Exp:16338

プログラマー友の中釣りが一番上手、釣友の中インフラが一番得意、インフラ友の中猫が一番好き、猫友の中プログラミングが一番詳しい。

本当の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のエンドポイントを叩くことでインフラ構築が自動で行われる、なんてこともできちゃいます。
(これはまた別の機会にご紹介します)

それではみなさんも是非試してみてください!!

クラウドエースでは最先端の技術を的確に扱うことができるエンジニア、ともにそこを目指すエンジニアを募集しています!!

次の記事を読み込んでいます
次の記事を読み込んでいます
次の記事を読み込んでいます
次の記事を読み込んでいます
次の記事を読み込んでいます