Skip to content

アプリケーションテンプレートでのCI/CDのセットアップ

スクリプト、アドホックコマンド、またはUIを介してカスタムアプリケーションを手動で管理することは、すぐに煩雑でエラーが発生しやすい作業となりかねません。 各デプロイでは、多くの場合、ミスを避けるために、慎重な調整、手順の繰り返し、および継続的なダブルチェックが必要です。 このような手作業によるプロセスは、時間の経過とともに、人為的ミスや一貫性のない環境、偶発的なダウンタイムのリスクを増大させる可能性があります。

アプリケーションテンプレートの目的は、独自のアプリケーションを構築するためのブループリントを提供することです。 これらのブループリントは「すぐに使える状態」の出発点を提供し、そこからワークフローをカスタマイズし拡張することが可能です。 Codespaceで作業すると、アプリケーションテンプレートの使用を簡単に開始できます。 実験段階が進んでいくと、変更内容を基に本番環境レベルのDevOpsを構築することになります。

このチュートリアルでは、2つのソース管理プラットフォーム(GitLab CI/CDパイプラインを備えたGitLabと、GitHub Actionsを備えたGitHub)における継続的インテグレーションおよび継続的デリバリー(CI/CD)の設定方法について説明します。

このチュートリアルの目的は以下のとおりです。

  1. マージリクエストまたはプルリクエストのチェックを有効にし、テスト、静的コード解析、およびリンティングを実施します。
  2. 「レビューアプリケーション」を有効にします。これにより、開発者が提案された変更を検証および実証できるよう、ブランチから完全なスタックが専用の名前でデプロイされます。
  3. 継続的に更新される共有アプリケーションデプロイへのマージ時に、継続的デリバリーを有効にします。
  4. 使用している環境に関係なく、クラウド対応のPulumiのState管理を活用して、共有された安全なPulumiスタック追跡を行う方法を説明します。
  5. CI/CDプラットフォームのシークレットと可変ストレージシステムをアプリケーションテンプレートと統合して、資格情報を安全に管理する方法を紹介します。

このチュートリアルでは、「データと会話する」エージェントアプリケーションテンプレートのフォークをベースとして、各プラットフォームでこれらの目標に取り組む方法を説明します。

GitLabでのマージリクエストのテストとリンターを設定する

コードの例

GitLabでのマージリクエストのすべてのコードとライブサンプルは、こちらから入手できます。

CIのテストをローカル開発と同じにするため、Taskを使ってプロセスを単純化します。 Taskは、シンプルな定義を用いたタスク実行のための、互換性があり軽量のツールです。 「データと会話する」エージェントには、Reactフロントエンド向けにPythonとTypeScriptの両方のコンポーネントがあります。Taskにより、環境のセットアップが簡素化され、1つのスペースからアプリケーションテンプレートのすべてを実行できるようになります。

このチュートリアルでは、ルートのTaskfileReact/Typescript Taskfileを提供します。 これらは相互に組み込まれ、.gitlab-ci.ymlで簡単に参照できます。

ルートのTaskfile.ymlにある以下のコードスニペットは、マージリクエストごとの迅速かつ安価なテストとリンティングの概要を示しています。

version: '3'
dotenv:
  - .env
includes:
  react:
    taskfile: ./frontend_react/react_src/Taskfile.yaml
    dir: ./frontend_react/react_src/
tasks:
...
  python-lint:
    desc: 🧹 Lint Python code
    cmds:
        - ruff format .
        - ruff check . --fix
        - mypy --pretty .
  python-lint-check:
    desc: 🧹 Lint Python code with fixes
    cmds:
        - ruff format --check .
        - ruff check .
        - mypy --pretty .
  lint:
    deps:
      - react:lint
      - python-lint
    desc: 🧹 Lint all code
  lint-check:
    deps:
      - react:lint-check
      - python-lint-check
    desc: 🧹 Lint all code 

上記のスニペットには、React/Typescript Taskfileが含まれており、そのタスクを取り込み、リンティングのためのPythonタスクを定義します。 これで、以下の.gitlab-ci.ymlに示すように、Taskfileからこれらのタスクを使用して、GitLabでリンターを実行するために必要なものを定義できます。

image: cimg/python:3.11-node

before_script:
  - pip install go-task-bin
  - task install
  - source .venv/bin/activate

lint:
  stage: check
  script:
    - task lint-check
  only:
    - merge_requests 

上記のスニペットでは、Taskをプリインストールし、before_scriptを使用してアプリテンプレートの依存関係をインストールし、リンティングを設定します。 その後、同じパターンに従ってテストできます。

test:
  stage: check
  script:
    - task test
  only:
    - merge_requests 

上記のスニペットでは、テストとリンティングの両方に同じcheckステージを使用しています。これにより両方が並行して実行されて、マージリクエストのチェックが高速化され、より迅速なフィードバックが提供されます。

デプロイの確認

merge_requestsのテストを設定したら、「レビューアプリ」のサポートを追加できます。このセクションを使用して、PRに表示される「手動」手順を追加します。

このタスク/パイプラインにより、開発者はボタンをクリックするだけで「データと会話する」スタック全体を起動し、同僚と共有したり、自分で検証したりすることが可能になります。

以下のコード例は、.gitlab-ci.ymlから抜粋したものです。 .env-templateに必要な変数を定義する必要があります。

variables:
  DATAROBOT_ENDPOINT: https://app.datarobot.com/api/v2
  FRONTEND_TYPE: react
  # The following variables are set on the GitLab CI/CD settings page:
  # DATAROBOT_API_TOKEN: "$DATAROBOT_API_TOKEN"
  # PULUMI_CONFIG_PASSPHRASE: "$PULUMI_CONFIG_PASSPHRASE"
  # OPENAI_API_VERSION: "$OPENAI_API_VERSION"
  # OPENAI_API_BASE: "$OPENAI_API_BASE"
  # OPENAI_API_DEPLOYMENT_ID: "$OPENAI_API_DEPLOYMENT_ID"
  # OPENAI_API_KEY: "$OPENAI_API_KEY"
  # # Used for the Pulumi DIY backend bucket: https://www.pulumi.com/docs/iac/concepts/state-and-backends/#azure-blob-storage
  # AZURE_STORAGE_ACCOUNT: "$AZURE_STORAGE_ACCOUNT"
  # AZURE_STORAGE_KEY: "$AZURE_STORAGE_KEY"
  # GITLAB_API_TOKEN: "$GITLAB_API_TOKEN" 

GitLabでは、プロジェクトのci_cd variables設定でこれらの変数を定義します。

これらの変数はCIとCDの両方に再利用し、Pulumiとアプリケーションに必要な情報をカバーします(たとえば、LLMキーなどの必要なデータ接続情報)。

変数が設定されたら、手作業の段階に進みます。

review_app:
  stage: review
  script:
  # Installs a "MR review app" for the branch being merged into main
    - curl -fsSL https://get.pulumi.com | sh
    - export PATH="~/.pulumi/bin:$PATH"
    - pulumi login --cloud-url "azblob://dr-ai-apps-pulumi"
    - pulumi stack select --create gitlab-mr-$CI_MERGE_REQUEST_IID
    - pulumi up --yes --stack gitlab-mr-$CI_MERGE_REQUEST_IID
    - echo "Deploying review app for branch gitlab-mr-$CI_MERGE_REQUEST_IID"
    - STACK_OUTPUT="<br><br>$(pulumi stack output --shell)"
    - STACK_OUTPUT="${STACK_OUTPUT//$'\n'/<br>}"
    - |
      curl --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" \
         --data "body=Review Deployment: $STACK_OUTPUT" \
         "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
  only:
    - merge_requests
  when: manual 

このスニペットでは、when:manualパイプラインステージを作成して、リソースの保存をオプションにします。 これは、開発者がリソースの節約が必要と判断した場合にのみ行ってください。 このスニペットは、Pulumiをインストールし、マージリクエスト番号に関連付けられたスタックを作成して一意性を確保し、スタックを立ち上げます。その後、アプリケーションをレビューするためのリンクとともに、リクエストについてコメントします。 このライブの実行例については、MR #5を参照してください。

開発者がマージリクエストを更新すると、このステージを再実行することで、Pulumi IaCおよびDataRobotの宣言型APIのべき等性に依存するアプリケーションテンプレートが更新されます。

スタックを破棄するために、マージリクエスト内および事後のクリーンアップ用に、手動で実行可能なジョブが用意されています。 下記のスニペットでは、マージリクエスト番号を手動変数として使用し、その後pulumi destroyおよびpulumi stack rmによってこれを破棄します。それにより、一元管理されたPulumiバックエンドをクリーンな状態に保ち、リソース使用量を最小限に抑えることができます。

destroy_review_app:
  stage: cleanup
  script:
    - curl -fsSL https://get.pulumi.com | sh
    - export PATH="~/.pulumi/bin:$PATH"
    - pulumi login --cloud-url "azblob://dr-ai-apps-pulumi"
    - pulumi destroy --yes --stack gitlab-mr-$CI_MERGE_REQUEST_IID
    - pulumi stack rm gitlab-mr-$CI_MERGE_REQUEST_IID --yes
    - echo "Destroyed review app stack for MR gitlab-mr-$CI_MERGE_REQUEST_IID"
  only:
    - merge_requests
    - main
  when: manual
  needs:
    - job: review_app
      optional: true 

継続的デリバリー

継続的デリバリーの設定は、レビューアプリの設定と似ていますが、マージ時に実行される点と、破棄メカニズムがない点が異なります。 すべての変更がマージされるため、永続的に維持されます。

以下の関連するパイプラインのyamlを確認してください。

deploy_ci:
  stage: deploy
  script:
    - curl -fsSL https://get.pulumi.com | sh
    - export PATH="~/.pulumi/bin:$PATH"
    - pulumi login --cloud-url "azblob://dr-ai-apps-pulumi"
    - pulumi stack select ci
    - pulumi up --yes --stack ci
    - echo "Deployed CI stack"
  only:
    - main
  when: on_success 

パイプラインは、スタック名が固定されているため、レビューアプリよりも簡単です。また、この例では、スタックを永続的に保存するためにAzure Blobストレージを使用しています。 また、実行例を確認することもできます。

GitHub Actions

GitHub Actionsは、GitLabのパイプラインとよく似ています。 この例を参考に、異なるPulumiバックエンドやGitHub ActionsのCI/CD設定を使用しても、同様の結果を得ることができます。

name: Pulumi Deployment
on:
  pull_request:
    types: [opened, synchronize, reopened]
env:
  PULUMI_STACK_NAME: github-pr-${{ github.event.repository.name }}-${{ github.event.number }}
jobs:
  update:
    name: pulumi-update-stack
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Decrypt Secrets
        run: gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output .env .env.gpg
        env:
          # https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions
          LARGE_SECRET_PASSPHRASE: ${{  secrets.LARGE_SECRET_PASSPHRASE }}
      - uses: actions/setup-python@v5
        with:
          python-version: 3.12
      - name: Install Pulumi
        run: |
          curl -fsSL https://get.pulumi.com | sh
          echo "$HOME/.pulumi/bin" >> $GITHUB_PATH
      - name: Setup Project Dependencies
        run: |
          command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
          uv venv .venv
          source .venv/bin/activate
          uv pip install -r requirements.txt
      - name: Plan Pulumi Update
        id: plan_pulumi_update
        run: |
          source .venv/bin/activate
          export $(grep -v '^#' .env | xargs)
          pulumi stack select --create $PULUMI_STACK_NAME
          pulumi up --yes
          # Store JSON output once and parse it for all values
          PULUMI_OUTPUT=$(pulumi stack output --json)
          APPLICATION_URL=$(echo "$PULUMI_OUTPUT" | jq -r 'to_entries[] | select(.key | startswith("Data Analyst Application")) | .value')
          DEPLOYMENT_URL=$(echo "$PULUMI_OUTPUT" | jq -r 'to_entries[] | select(.key | startswith("Generative Analyst Deployment")) | .value')
          APP_ID=$(echo "$PULUMI_OUTPUT" | jq -r '.DATAROBOT_APPLICATION_ID // empty')
          LLM_ID=$(echo "$PULUMI_OUTPUT" | jq -r '.LLM_DEPLOYMENT_ID // empty')
          echo "application_url=${APPLICATION_URL}" >> $GITHUB_OUTPUT
          echo "deployment_url=${DEPLOYMENT_URL}" >> $GITHUB_OUTPUT
          echo "app_id=${APP_ID}" >> $GITHUB_OUTPUT
          echo "llm_id=${LLM_ID}" >> $GITHUB_OUTPUT
        env:
          PULUMI_ACCESS_TOKEN: ${{  secrets.PULUMI_ACCESS_TOKEN }}
      - name: Comment PR with App URL
        uses: peter-evans/create-or-update-comment@v4
        with:
          token: ${{  secrets.GITHUB_TOKEN }}
          issue-number: ${{  github.event.number }}
          body: |
            # 🚀 Your application is ready!
            ## Application Info
            - **Application URL:** [${{  steps.plan_pulumi_update.outputs.application_url }}](${{  steps.plan_pulumi_update.outputs.application_url }})
            - **Application ID:** `${{  steps.plan_pulumi_update.outputs.app_id }}`
            ## LLM Deployment
            - **Deployment URL:** [${{  steps.plan_pulumi_update.outputs.deployment_url }}](${{  steps.plan_pulumi_update.outputs.deployment_url }})
            - **Deployment ID:** `${{  steps.plan_pulumi_update.outputs.llm_id }}`
            ### Pulumi Stack
            - **Stack Name:** `${{  env.PULUMI_STACK_NAME }}` 

これで、すべてのプルリクエストでpulumi upが実行され、Pulumiスタックがクラウドにバックアップされるようになりました。これにより、コミット間の追跡が可能になります。 Pulumiが変更を完了すると、アプリに関する情報が表示されます。 実際のワークフローの動作を確認するには、このGitHubプルリクエストを参照してください。

追加の設定が必要な注意事項があります。これらは以下のセクションで説明します。

Pulumi

Pulumiをローカルに設定する最も簡単でわかりやすい方法は、Pulumiをインストールし、Pulumiクラウドバックエンドにログインすることです。 これにより、ローカルのPulumiスタックがクラウドバックアップと同期され、他のエッジノード(GitHub Actionsなど)からスタックにアクセスできるようになります。 GitHubのリポジトリシークレットPULUMI_ACCESS_TOKENシークレットを追加するだけです。

それがセキュリティ上の懸念である場合、またはデフォルトのPulumi Cloudアプローチを使用しない他の理由(使用環境からのネットワークアクセス、コスト、企業ポリシーなど)がある場合は、DIY Pulumiバックエンドを利用できます。 たとえば、GitLabチュートリアルでは、Azure DIYバックエンドを使っていますが(このセクションのコード例の11行目)、GitHubではAzureやAWSのバックエンドを使うことができます。

GitHub CI/CDでは、インフラストラクチャの変更については、推奨されているPulumiベースの方法を重視していますが、S3や他のPulumi Stateプロバイダーを利用したい場合は、たとえばAzureを利用してGitLab DIYの方法に従うことができます。

GitHub Actionsのシークレット

APIキー、データベースの資格情報、クラウドプロバイダートークンなどの機密データを管理する場合、GitHub Actionsにはシークレットを処理するための組み込みの方法が用意されています。 最も簡単な方法は、各シークレットをGitHubに個別に保存し、環境変数としてワークフローに組み込むことです。 この方法は、シークレットの数が増えるにつれて、煩雑でミスが起こりやすくなる可能性があります。 変数の設定を誤ったり、ログに値が漏れたり、実際に使用されている内容を把握できなくなったりすることはよくあります。

GitHub自体で推奨しているより堅牢な代替手段は、シークレットをファイル(.envなど)にまとめ、GPGで暗号化して、暗号化されたバージョンのみをリポジトリに保存することです。 こうすることで、必要な復号キーはGitHub Secretsに1つだけとなり、残りのシークレットはワークフローファイルに分散することなく、厳重に管理およびバージョニングされます。 これは、リスクを軽減し、規模に応じて管理を簡素化する、クリーンで監査可能なアプローチです。

以下の手順では、その方法の概要を説明します。

  1. gpg --symmetric --cipher-algo AES256 .envを実行します。 パスフレーズの入力を求められます。 パスフレーズを値として使用する新しいシークレットをGitHubで作成する必要があるため、パスフレーズを覚えておいてください。

  2. パスフレーズを含む新しいシークレットをGitHubリポジトリに作成します。 たとえば、LARGE_SECRET_PASSPHRASEという名前で新しいシークレットを作成し、シークレットの値を前の手順で使用したパスフレーズに設定します。

  3. 暗号化されたファイルをリポジトリ内のパスにコピーし、コミットします。 この例では、暗号化されたファイルは.env.gpgです。

git add .env.gpg
git commit -m "Add new GPG encrypted secret file"
git push 

GitHub Actionsでリソースを破棄する

このセクションでは、実際のアプリケーションを含むすべてのベースリソースを使用してユースケースを正常に作成した後、そのユースケースを削除する方法について説明します。 GitHub Actionsを使って、手動トリガーによるワークフローを作成します。

前回のpulumi upの例ではGPGで暗号化されたシークレットを使っていましたが、今回の手動ワークフローではGitHubの環境変数を直接使います。 ワークフローには、入力としてスタック名が必要です。 GitHubシークレットのPULUMI_ACCESS_TOKENを使って、Pulumi Cloudで認証します。

name: Pulumi Stack Destroy
on:
  workflow_dispatch:
    inputs:
      stack_name:
        description: 'Stack name to destroy (e.g. github-pr-foobar-42)'
        required: true
        type: string

env:
  PULUMI_STACK_NAME: github-pr-${{  github.event.repository.name }}-${{  github.event.number }}
  PULUMI_ACCESS_TOKEN: ${{  secrets.PULUMI_ACCESS_TOKEN }}

jobs:
  destroy:
    name: pulumi-destroy-stack
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: 3.12
      - name: Install Pulumi
        run: |
          curl -fsSL https://get.pulumi.com | sh
          echo "$HOME/.pulumi/bin" >> $GITHUB_PATH
      - name: Setup Project Dependencies
        run: |
          command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
          uv venv .venv
          source .venv/bin/activate
          uv pip install -r requirements.txt
      - name: Destroy Pulumi Stack
        run: |
          source .venv/bin/activate
          pulumi stack select ${{  github.event.inputs.stack_name }}
          pulumi destroy --yes
          pulumi stack rm --yes ${{  github.event.inputs.stack_name }} 

PRで受け取ったスタック名でフローをトリガーするだけで、ユースケースに関連付けられたすべてのリソースを安全に破棄できます。

PulumiのState管理

一元化されたPulumi Stateバックエンドを使用することをお勧めします。 主な理由の1つは、プロビジョニングしたリソースを見失うことなく、マシン間、CI/CDプラットフォーム間、およびCodespaceの内外でPulumiを使用できることです。

共有ストレージのPulumiバックエンドを使用すると、DataRobotアプリケーションで新しいCodespaceを独自のコードでスピンアップして、新しいエクスペリメントを作成したり、既存のスタックを更新したりすることができます。 その方法は、必要なコードのクローンを作成し、pulumi login <backend>を実行した後、以下のスニペットを使用するだけです。

 pulumi stack ls -a 

次のような結果になります。

 NAME                                 LAST UPDATE   RESOURCE COUNT
organization/agent_sre/sredev1       2 months ago  0
organization/talk-to-my-docs/x-dev0  2 days ago    12
dev0                           2 weeks ago   13
ci    6 days ago   13 

その後、以下の簡単なコマンドを実行します。

pulumi stack select ci 

このコードによって、そのスタックに直接アクセスし、簡単なpulumi upで変更を同期できます。

ここに挙げた例を参考に、この方法でレビューアプリの管理や更新ができます。

成果

開発者と組織にとって、AIアプリケーションにDevOpsのベストプラクティスを導入することは、AIの可能性を最大限に引き出す上で不可欠です。 最新のCI/CDパイプライン、レビューアプリフロー、インフラストラクチャ・アズ・コードをPulumiと統合することで、組織がAI搭載ソリューションを構築、テスト、提供する方法が変わります。

DataRobotを中核とするこれらのDevOpsパターンにより、AIアプリに対するすべての変更が、本番環境に到達するはるか前に、実環境で自動的に検証され、安全にデプロイされ、簡単にレビューされるようになります。 レビューアプリを使用すると、シームレスでリスクのないコラボレーションが可能になります。また、クラウドベースのPulumi Stateとセキュリティで保護されたシークレット管理により、インフラストラクチャの安全性と管理性が大規模に維持されます。

これらのアプローチを採用することで、AIアプリケーションを迅速かつ確実に進化させることができ、すべてのチームメンバーが貢献と革新を行うことができます。 これらのパターンを繰り返し適用するうちに、AI向けの本番環境レベルのDevOpsが実現可能であるだけでなく、ユーザーに真の価値を安全かつ自信を持って、現代のビジネスのスピードで提供するための起爆剤にもなることがわかります。