PulumiでDigitalOceanおよびKubernetesインフラストラクチャを管理する方法

著者は、Write for DOnationsプログラムの一部として寄付を受け取るためにDiversity in Tech Fundを選択しました。

前書き

Pulumiは、汎用プログラミング言語で記述されたコードを使用してインフラストラクチャを作成、展開、および管理するためのツールです。 アプリケーションの構成に加えて、Droplet、管理対象データベース、DNSレコード、Kubernetesクラスターなど、DigitalOceanのすべての管理サービスの自動化をサポートしています。 展開は使いやすいコマンドラインインターフェイスから実行されます。このインターフェイスは、さまざまな一般的なCI / CDシステムとも統合されています。

Pulumiは複数の言語をサポートしていますが、このチュートリアルでは、Node.jsランタイムを使用する静的に型指定されたJavaScriptのバージョンであるTypeScriptを使用します。 つまり、IDEのサポートとコンパイル時のチェックが行われ、ユーティリティタスクのNPMモジュールにアクセスしながら、適切なリソースを構成したり、正しいスラッグを使用したりしたことを確認できます。

このチュートリアルでは、DigitalOceanKubernetesクラスター、負荷分散されたKubernetesアプリケーション、および選択した安定したドメイン名でアプリケーションを利用できるようにするDigitalOceanDNSドメインをプロビジョニングします。 これはすべて、60行のinfrastructure-as-codeと1つのpulumi upコマンドラインジェスチャでプロビジョニングできます。 このチュートリアルを完了すると、DigitalOceanとKubernetesの全領域を活用するPulumi as-codeを使用して強力なクラウドアーキテクチャを生産的に構築する準備が整います。

前提条件

このチュートリアルを実行するには、次のものが必要です。

  • リソースをデプロイするDigitalOceanアカウント。 まだ持っていない場合は、register here

  • 自動展開を実行するDigitalOcean APIトークン。 Generate a personal access token hereを作成し、手順2で使用するので手元に置いておきます。

  • Kubernetesクラスタを作成して使用するため、install kubectlを実行する必要があります。 それをさらに構成することを心配しないでください-後でそれを行います。

  • TypeScriptでインフラストラクチャとしてコードを記述するため、Node.js 8以降が必要になります。 Download it hereまたはusing your system’s package managerをインストールします。

  • Pulumiを使用してインフラストラクチャをデプロイするため、install the open source Pulumi SDKを実行する必要があります。

  • オプションのステップ5を実行するには、DigitalOceanネームサーバーを使用するように構成されたドメイン名が必要です。 This guideは、選択したレジストラに対してこれを行う方法を説明しています。

[[step-1 -—- scaffolding-a-new-project]] ==ステップ1—新しいプロジェクトの足場

最初のステップは、Pulumiプロジェクトを保存するディレクトリを作成することです。 このディレクトリには、プロジェクトとそのNPM依存関係を説明するメタデータファイルに加えて、インフラストラクチャ定義のソースコードが含まれます。

まず、ディレクトリを作成します。

mkdir do-k8s

次に、新しく作成されたディレクトリに移動します。

cd do-k8s

今後は、新しく作成したdo-k8sディレクトリからコマンドを実行します。

次に、新しいPulumiプロジェクトを作成します。 これを実現するにはさまざまな方法がありますが、最も簡単な方法は、typescriptプロジェクトテンプレートでpulumi newコマンドを使用することです。 このコマンドは、最初にPulumiにログインするように促し、プロジェクトとデプロイメントの状態を保存してから、現在のディレクトリに単純なTypeScriptプロジェクトを作成します。

pulumi new typescript -y

ここでは、-yオプションをnewコマンドに渡して、デフォルトのプロジェクトオプションを受け入れるように指示しています。 たとえば、プロジェクト名は現在のディレクトリの名前から取得されるため、do-k8sになります。 プロジェクト名にさまざまなオプションを使用する場合は、-yを削除するだけです。

コマンドを実行した後、ディレクトリの内容をlsで一覧表示します。

ls

次のファイルが存在するようになります。

OutputPulumi.yaml       index.ts          node_modules
package-lock.json package.json      tsconfig.json

編集するプライマリファイルはindex.tsです。 このチュートリアルではこの単一のファイルのみを使用しますが、Node.jsモジュールを使用して、プロジェクトを適切に編成することができます。 このチュートリアルでは、Pulumiが変更内容のみを検出して段階的に展開できるという事実を活用して、一度に1ステップずつ説明します。 必要に応じて、プログラム全体にデータを入力し、pulumi upを使用してすべてを一度にデプロイできます。

新しいプロジェクトを足場にしたので、チュートリアルを進めるために必要な依存関係を追加する準備ができました。

[[step-2 -—- adding-dependencies]] ==ステップ2—依存関係の追加

次のステップは、DigitalOceanおよびKubernetesパッケージのインストールと依存関係の追加です。 最初に、NPMを使用してインストールします。

npm install @pulumi/digitalocean @pulumi/kubernetes

これにより、NPMパッケージ、Pulumiプラグインがダウンロードされ、依存関係として保存されます。

次に、お気に入りのエディターでindex.tsファイルを開きます。 このチュートリアルではnanoを使用します。

nano index.ts

index.tsの内容を次のように置き換えます。

index.ts

import * as digitalocean from "@pulumi/digitalocean";
import * as kubernetes from "@pulumi/kubernetes";

これにより、これらのパッケージのすべての内容がプログラムで利用可能になります。 TypeScriptとNode.jsを理解するIDEを使用して"digitalocean."と入力すると、たとえば、このパッケージでサポートされているDigitalOceanリソースのリストが表示されます。

コンテンツを追加した後、ファイルを保存して閉じます。

[.note]#Note:これらのパッケージで利用可能なもののサブセットを使用します。 リソース、プロパティ、および関連するAPIの完全なドキュメントについては、@pulumi/digitaloceanおよび@pulumi/kubernetesパッケージの関連するAPIドキュメントを参照してください。

次に、Pulumiがアカウントのリソースをプロビジョニングできるように、DigitalOceanトークンを構成します。

pulumi config set digitalocean:token YOUR_TOKEN_HERE --secret

--secretフラグに注目してください。これは、Pulumiの暗号化サービスを使用してトークンを暗号化し、暗号文に格納されていることを確認します。 必要に応じて、代わりにDIGITALOCEAN_TOKEN環境変数を使用できますが、プログラムを更新するたびに設定することを忘れないでください。構成を使用すると、プロジェクトに自動的に保存されて使用されます。

このステップでは、Kubernetesクラスターをプロビジョニングできるように、必要な依存関係を追加し、PulumiでAPIトークンを構成しました。

[[step-3 -—- provisioning-a-kubernetes-cluster]] ==ステップ3—Kubernetesクラスターのプロビジョニング

これで、DigitalOcean Kubernetesクラスターを作成する準備が整いました。 index.tsファイルを再度開くことから始めます。

nano index.ts

index.tsファイルの最後に次の行を追加します。

index.ts

...
const cluster = new digitalocean.KubernetesCluster("do-cluster", {
    region: digitalocean.Regions.SFO2,
    version: "latest",
    nodePool: {
        name: "default",
        size: digitalocean.DropletSlugs.DropletS2VPCU2GB,
        nodeCount: 3,
    },
});

export const kubeconfig = cluster.kubeConfigs[0].rawConfig;

この新しいコードは、digitalocean.KubernetesClusterのインスタンスを割り当て、それにいくつかのプロパティを設定します。 これには、sfo2region sluglatestでサポートされているバージョンのKubernetes、s-2vcpu-2gbDroplet size slugの使用が含まれ、3つのDropletインスタンスの必要な数が示されます。 これらは自由に変更できますが、この記事の執筆時点では、DigitalOcean Kubernetesは特定の地域でのみ利用可能です。 リージョンの可用性に関する最新情報については、product documentationを参照できます。

クラスターで構成できるプロパティの完全なリストについては、KubernetesCluster API documentationを参照してください。

そのコードスニペットの最後の行は、結果のKubernetesクラスターのkubeconfig fileをエクスポートして、使いやすくします。 エクスポートされた変数はコンソールに出力され、ツールからもアクセスできます。 これを一時的に使用して、kubectlなどの標準ツールからクラスターにアクセスします。

これで、クラスターをデプロイする準備が整いました。 これを行うには、pulumi upを実行します。

pulumi up

このコマンドは、プログラムを取得し、説明されているインフラストラクチャを作成するための計画を生成し、それらの変更を展開するための一連の手順を実行します。 これは、後続の更新が行われたときにインフラストラクチャを比較および更新できることに加えて、インフラストラクチャの最初の作成でも機能します。 この場合、出力は次のようになります。

OutputPreviewing update (dev):

     Type                                     Name        Plan
 +   pulumi:pulumi:Stack                      do-k8s-dev  create
 +   └─ digitalocean:index:KubernetesCluster  do-cluster  create

Resources:
    + 2 to create

Do you want to perform this update?
  yes
> no
  details

これは、更新を続行すると、do-clusterという名前の単一のKubernetesクラスターが作成されることを示しています。 yes/no/detailsプロンプトを使用すると、実際に変更を加える前に、これが望ましい結果であることを確認できます。 detailsを選択すると、リソースとそのプロパティの完全なリストが表示されます。 yesを選択して、展開を開始します。

OutputUpdating (dev):

     Type                                     Name        Status
 +   pulumi:pulumi:Stack                      do-k8s-dev  created
 +   └─ digitalocean:index:KubernetesCluster  do-cluster  created

Outputs:
    kubeconfig: "..."

Resources:
    + 2 created

Duration: 6m5s

Permalink: https://app.pulumi.com/.../do-k8s/dev/updates/1

クラスターの作成には数分かかりますが、クラスターが稼働し、完全なkubeconfigがコンソールに出力されます。 kubeconfigをファイルに保存します。

pulumi stack output kubeconfig > kubeconfig.yml

次に、それをkubectlとともに使用して、Kubernetesコマンドを実行します。

KUBECONFIG=./kubeconfig.yml kubectl get nodes

次のような出力が表示されます。

OutputNAME           STATUS    ROLES     AGE       VERSION
default-o4sj   Ready         4m5s      v1.14.2
default-o4so   Ready         4m3s      v1.14.2
default-o4sx   Ready         3m37s     v1.14.2

この時点で、Infrastructure-as-Codeをセットアップし、新しいDigitalOcean Kubernetesクラスターを起動して構成するための繰り返し可能な方法を使用できました。 次のステップでは、これに基づいてコードでKubernetesインフラストラクチャを定義し、Kubernetesインフラストラクチャを同様に展開および管理する方法を学習します。

[[step-4 -—- deploying-an-application-to-your-cluster]] ==ステップ4—アプリケーションをクラスターにデプロイする

次に、Infrastructure-as-codeを使用してKubernetesアプリケーションの構成を説明します。 これは3つの部分で構成されます。

  1. kubectlが使用するように構成されているデフォルトではなく、KubernetesリソースをDigitalOceanクラスターにデプロイするようにPulumiに指示するProviderオブジェクト。

  2. Kubernetes Deployment。これは、任意の数のポッドにレプリケートされるDockerコンテナイメージをデプロイする標準のKubernetesの方法です。

  3. Kubernetes Service。これは、ポッドのターゲットセット(この場合は上記のデプロイ)全体でアクセスを負荷分散するようにKubernetesに指示する標準的な方法です。

これは、Kubernetesで負荷分散サービスを起動して実行するためのかなり標準的なreference architectureです。

これら3つすべてをデプロイするには、index.tsファイルを再度開きます。

nano index.ts

ファイルが開いたら、このコードをファイルの最後に追加します。

index.ts

...
const provider = new kubernetes.Provider("do-k8s", { kubeconfig })

const appLabels = { "app": "app-nginx" };
const app = new kubernetes.apps.v1.Deployment("do-app-dep", {
    spec: {
        selector: { matchLabels: appLabels },
        replicas: 5,
        template: {
            metadata: { labels: appLabels },
            spec: {
                containers: [{
                    name: "nginx",
                    image: "nginx",
                }],
            },
        },
    },
}, { provider });
const appService = new kubernetes.core.v1.Service("do-app-svc", {
    spec: {
        type: "LoadBalancer",
        selector: app.spec.template.metadata.labels,
        ports: [{ port: 80 }],
    },
}, { provider });

export const ingressIp = appService.status.loadBalancer.ingress[0].ip;

このコードは、標準のKubernetes構成に似ており、オブジェクトとそのプロパティの動作は同等です。ただし、他のインフラストラクチャ宣言とともにTypeScriptで記述されている点が異なります。

変更を行った後、ファイルを保存して閉じます。

前と同じように、pulumi upを実行して変更をプレビューし、展開します。

pulumi up

続行するためにyesを選択した後、CLIは、ポッドの可用性、IPアドレスの割り当てなどに関する診断を含む、詳細なステータス更新を出力します。 これは、展開が完了するのに時間がかかったり、停止したりする理由を理解するのに役立ちます。

完全な出力は次のようになります。

OutputUpdating (dev):

     Type                            Name        Status
     pulumi:pulumi:Stack             do-k8s-dev
 +   ├─ pulumi:providers:kubernetes  do-k8s      created
 +   ├─ kubernetes:apps:Deployment   do-app-dep  created
 +   └─ kubernetes:core:Service      do-app-svc  created

Outputs:
  + ingressIp : "157.230.199.202"

Resources:
    + 3 created
    2 unchanged

Duration: 2m52s

Permalink: https://app.pulumi.com/.../do-k8s/dev/updates/2

これが完了したら、必要な数のPodが実行されていることに注意してください。

KUBECONFIG=./kubeconfig.yml kubectl get pods
OutputNAME                                   READY     STATUS    RESTARTS   AGE
do-app-dep-vyf8k78z-758486ff68-5z8hk   1/1       Running   0          1m
do-app-dep-vyf8k78z-758486ff68-8982s   1/1       Running   0          1m
do-app-dep-vyf8k78z-758486ff68-94k7b   1/1       Running   0          1m
do-app-dep-vyf8k78z-758486ff68-cqm4c   1/1       Running   0          1m
do-app-dep-vyf8k78z-758486ff68-lx2d7   1/1       Running   0          1m

プログラムがクラスターのkubeconfigファイルをエクスポートする方法と同様に、このプログラムはKubernetesサービスの結果のロードバランサーのIPアドレスもエクスポートします。 これを使用してエンドポイントをcurlし、それが稼働していることを確認します。

curl $(pulumi stack output ingressIp)
Output


Welcome to nginx!



Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

ここから、アプリケーションインフラストラクチャを簡単に編集および再デプロイできます。 たとえば、replicas: 5行をreplicas: 7に変更してから、pulumi upを再実行してみてください。

pulumi up

変更内容を表示するだけで、詳細を選択すると正確な差分が表示されることに注意してください。

OutputPreviewing update (dev):

     Type                           Name        Plan       Info
     pulumi:pulumi:Stack            do-k8s-dev
 ~   └─ kubernetes:apps:Deployment  do-app-dep  update     [diff: ~spec]

Resources:
    ~ 1 to update
    4 unchanged

Do you want to perform this update? details
  pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:dev::do-k8s::pulumi:pulumi:Stack::do-k8s-dev]
    ~ kubernetes:apps/v1:Deployment: (update)
        [id=default/do-app-dep-vyf8k78z]
        [urn=urn:pulumi:dev::do-k8s::kubernetes:apps/v1:Deployment::do-app-dep]
        [provider=urn:pulumi:dev::do-k8s::pulumi:providers:kubernetes::do-k8s::80f36105-337f-451f-a191-5835823df9be]
      ~ spec: {
          ~ replicas: 5 => 7
        }

これで、完全に機能するKubernetesクラスターと動作するアプリケーションの両方ができました。 アプリケーションが稼働している状態で、アプリケーションで使用するカスタムドメインを構成できます。 次のステップでは、Pulumiを使用してDNSを構成します。

[[step-5 -—- creating-a-dns-domain-optional]] ==ステップ5— DNSドメインの作成(オプション)

Kubernetesクラスターとアプリケーションは稼働していますが、アプリケーションのアドレスはクラスターによる自動IPアドレス割り当ての気まぐれに依存しています。 調整して再デプロイすると、このアドレスが変わる可能性があります。 このステップでは、カスタムDNS名をロードバランサーのIPアドレスに割り当てて、インフラストラクチャを後で変更しても安定するようにする方法を確認します。

[.note]#Note:この手順を完了するには、DigitalOceanのDNSネームサーバー、ns1.digitalocean.comns2.digitalocean.com、およびns3.digitalocean.comを使用するドメインがあることを確認してください。 これを構成する手順は、「前提条件」セクションにあります。

DNSを構成するには、index.tsファイルを開き、ファイルの最後に次のコードを追加します。

index.ts

...
const domain = new digitalocean.Domain("do-domain", {
    name: "your_domain",
    ipAddress: ingressIp,
});

このコードは、KubernetesサービスのIPアドレスを参照するAレコードを持つ新しいDNSエントリを作成します。 このスニペットのyour_domainを、選択したドメイン名に置き換えます。

wwwなどの追加のサブドメインがWebアプリケーションを指すようにするのが一般的です。 これは、DigitalOcean DNSレコードを使用して簡単に達成できます。 この例をより面白くするために、www.your_domain.comyour_domain.comを指すCNAMEレコードも追加します。

index.ts

...
const cnameRecord = new digitalocean.DnsRecord("do-domain-cname", {
    domain: domain.name,
    type: "CNAME",
    name: "www",
    value: "@",
});

これらの変更を行った後、ファイルを保存して閉じます。

最後に、pulumi upを実行して、既存のアプリケーションとクラスターを指すようにDNS変更を展開します。

OutputUpdating (dev):

     Type                             Name             Status
     pulumi:pulumi:Stack              do-k8s-dev
 +   ├─ digitalocean:index:Domain     do-domain        created
 +   └─ digitalocean:index:DnsRecord  do-domain-cname  created

Resources:
    + 2 created
    5 unchanged

Duration: 6s

Permalink: https://app.pulumi.com/.../do-k8s/dev/updates/3

DNSの変更が反映されると、カスタムドメインでコンテンツにアクセスできるようになります。

curl www.your_domain.com

次のような出力が表示されます。

Output


Welcome to nginx!



Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

これで、新しいDigitalOcean Kubernetesクラスターを正常にセットアップし、負荷分散されたKubernetesアプリケーションをデプロイし、そのアプリケーションのロードバランサーにDigitalOcean DNSを使用した安定したドメイン名を、すべて60行のコードとpulumi upで指定しました。コマンド。

次のステップでは、不要になったリソースを削除します。

[[step-6 -—- removing-the-resources-optional]] ==ステップ6—リソースの削除(オプション)

チュートリアルを終了する前に、上記で作成したすべてのリソースを破棄することができます。 これにより、使用されていないリソースに対して課金されることがなくなります。 アプリケーションを稼働したままにする場合は、この手順をスキップしてください。

次のコマンドを実行して、リソースを破棄します。 これは元に戻せないため、使用には注意してください!

pulumi destroy

upコマンドの場合と同様に、destroyは、アクションを実行する前にプレビューとプロンプトを表示します。

OutputPreviewing destroy (dev):

     Type                                     Name             Plan
 -   pulumi:pulumi:Stack                      do-k8s-dev       delete
 -   ├─ digitalocean:index:DnsRecord          do-domain-cname  delete
 -   ├─ digitalocean:index:Domain             do-domain        delete
 -   ├─ kubernetes:core:Service               do-app-svc       delete
 -   ├─ kubernetes:apps:Deployment            do-app-dep       delete
 -   ├─ pulumi:providers:kubernetes           do-k8s           delete
 -   └─ digitalocean:index:KubernetesCluster  do-cluster       delete

Resources:
    - 7 to delete

Do you want to perform this destroy?
  yes
> no
  details

これが必要なものであると想定して、yesを選択し、削除が発生するのを確認します。

OutputDestroying (dev):

     Type                                     Name             Status
 -   pulumi:pulumi:Stack                      do-k8s-dev       deleted
 -   ├─ digitalocean:index:DnsRecord          do-domain-cname  deleted
 -   ├─ digitalocean:index:Domain             do-domain        deleted
 -   ├─ kubernetes:core:Service               do-app-svc       deleted
 -   ├─ kubernetes:apps:Deployment            do-app-dep       deleted
 -   ├─ pulumi:providers:kubernetes           do-k8s           deleted
 -   └─ digitalocean:index:KubernetesCluster  do-cluster       deleted

Resources:
    - 7 deleted

Duration: 7s

Permalink: https://app.pulumi.com/.../do-k8s/dev/updates/4

この時点では、何も残っていません。DNSエントリはなくなり、Kubernetesクラスターは、その内部で実行されているアプリケーションとともに、消えています。 パーマリンクはまだ利用可能ですので、戻ってこのスタックの更新の完全な履歴を見ることができます。 これは、サービスがすべてのリソースの完全な状態履歴を保持するため、破壊が間違いだった場合の回復に役立ちます。

プロジェクト全体を破壊する場合は、スタックを削除します。

pulumi stack rm

スタックの名前を入力して、削除の確認を求める出力を受け取ります。

OutputThis will permanently remove the 'dev' stack!
Please confirm that this is what you'd like to do by typing ("dev"):

クラウドインフラストラクチャリソースを削除するdestroyコマンドとは異なり、スタックを削除すると、Pulumiの範囲からスタックの完全な履歴が完全に消去されます。

結論

このチュートリアルでは、このクラスターを使用するKubernetesアプリケーション構成に加えて、DigitalOceanインフラストラクチャリソース(KubernetesクラスターとAおよびCNAMEレコードを持つDNSドメイン)をデプロイしました。 既存のエディタ、ツール、およびライブラリで動作し、既存のコミュニティとパッケージを活用する、使い慣れたプログラミング言語TypeScriptで記述されたコードとしてのインフラストラクチャを使用してこれを行いました。 アプリケーションとインフラストラクチャにまたがる展開を行うために、単一のコマンドラインワークフローを使用してすべてを完了しました。

ここから、次のステップを実行できます。

このチュートリアルのサンプル全体はavailable on GitHubです。 今日の独自のプロジェクトでPulumiInfrastructure-as-Codeを使用する方法の詳細については、Pulumi DocumentationTutorials、またはGetting Startedガイドを確認してください。 Pulumiはオープンソースであり、無料で使用できます。

Related