継続的デリバリーにより、開発者、チーム、および組織は簡単にコードを更新し、顧客に新しい機能をリリースできます。チームや組織が CI/CD および DevOps プラクティスを採用すると、文化が変革され、これらのメリットを実現できます。CI/CD および DevOps のプラクティスを実装することにより、チームは、最新のツールを活用し、信頼性と一貫性のある方法で、ソフトウェアをビルド、テスト、およびデプロイできます。
Infrastructure as code(IaC)を使用すると、チームは、コード内でクラウドリソースを静的に定義および宣言し、コードからこれらのクラウドリソースをデプロイして、動的に維持でき、これらのリソースを簡単に管理できます。このブログでは、CI/CD パイプライン内で IaC を実装する方法について説明します。このブログでは、CircleCI のパートナーである Pulumi の Orb をパイプラインに実装する方法も説明します。この Orb は、アプリケーションを定義し、Google Kubernetes Engine(GKE)クラスタにデプロイします。Pulumi は、このブログで説明する IaC コンポーネントを提供します。
前提条件
このブログは、Git リポジトリがすでにあり、GitHub でホスティングされていることを前提としています。ここで提供しているコードサンプルは、プロジェクトのリポジトリ内のディレクトリに配置する必要があります。
使用するテクノロジー
このブログでは、以下について基本的な理解をしていることも前提としています。
Pulumi のセットアップ
Pulumi を使用すると、開発者は自分の好きな言語(JavaScript、Python、Go など)でコードを記述し、専用の DSL や YAML テンプレートソリューションを習得しなくても、クラウドアプリとインフラストラクチャを簡単にデプロイできます。ファーストクラス言語を使用すると、抽象化と再利用が可能になります。Pulumi は、すべての主要なクラウドサービスプロバイダー(AWS、Azure、GCP など)に対応するローレベルのリソース定義に加えて、ハイレベルのクラウドパッケージを提供しており、1 つのシステムをマスターするだけで、これらのすべてのプロバイダーにデリバリーできるようになります。
選択したアプリケーションリポジトリで、Pulumi アプリを配置する新しいディレクトリを作成します。
mkdir -p pulumi/gke
cd pulumi/gke
次に、Pulumi アカウントと Google Cloud アカウントにサインアップします(サインアップをしたことがない場合)。
- 新しい Pulumi アカウントにサインアップするか、ログインします。
- Pulumi をローカルにインストールします。
- Python 言語ランタイムをインストールします。
- 新しい Pulumi プロジェクト
新しい Pulumi プロジェクトを作成すると、pulumi/gke
ディレクトリに次の 3 つのファイルが作成されます。
- Pulumi.yaml - プロジェクトに関するメタデータを指定します。
- Pulumi.<スタック名>.yaml - 初期化したスタックの構成値が含まれます。 <stack name> は、新しい Pulumi プロジェクトを作成するときに定義したスタック名に置き換える必要があります。このチュートリアルでは、このファイルに
Pulumi.k8s.yaml
という名前を付けています。 - __main__.py - スタックリソースを定義する Pulumi プログラム。これは、IaC magicを実現する場所です。
Pulumi.<スタック名>.yaml
ファイルを編集し、次のコードを貼り付けます。
config:
gcp:credentials: ./cicd_demo_gcp_creds.json
gcp:project: cicd-workshops
gcp:region: us-east1
gcp:zone: us-east1-d
gke:name: k8s
__main__.py
ファイルを編集し、その内容を次の内容に置き換えます。
import os
import pulumi
import pulumi_kubernetes
from pulumi import ResourceOptions
from pulumi_kubernetes.apps.v1 import Deployment
from pulumi_kubernetes.core.v1 import Namespace, Pod, Service
from pulumi_gcp import container
conf = pulumi.Config('gke')
gcp_conf = pulumi.Config('gcp')
stack_name = conf.require('name')
gcp_project = gcp_conf.require('project')
gcp_zone = gcp_conf.require('zone')
app_name = 'cicd-app'
app_label = {'appClass':app_name}
cluster_name = app_name
image_tag = ''
if 'CIRCLE_SHA1' in os.environ:
image_tag = os.environ['CIRCLE_SHA1']
else:
image_tag = 'latest'
docker_image = 'ariv3ra/orb-pulumi-gcp:{0}'.format(image_tag)
machine_type = 'g1-small'
cluster = container.Cluster(
cluster_name,
initial_node_count=3,
min_master_version='latest',
node_version='latest',
node_config={
'machine_type': machine_type,
'oauth_scopes': [
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/devstorage.read_only",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring",
],
}
)
# Set the Kubeconfig file values here
def generate_k8_config(master_auth, endpoint, context):
config = '''apiVersion: v1
clusters:
- cluster:
certificate-authority-data: {masterAuth}
server: https://{endpoint}
name: {context}
contexts:
- context:
cluster: {context}
user: {context}
name: {context}
current-context: {context}
kind: Config
preferences: {prefs}
users:
- name: {context}
user:
auth-provider:
config:
cmd-args: config config-helper --format=json
cmd-path: gcloud
expiry-key: '{expToken}'
token-key: '{tokenKey}'
name: gcp
'''.format(masterAuth=master_auth, context=context, endpoint=endpoint,
prefs='{}', expToken = '{.credential.token_expiry}', tokenKey='{.credential.access_token}')
return config
gke_masterAuth = cluster.master_auth['clusterCaCertificate']
gke_endpoint = cluster.endpoint
gke_context = gcp_project+'_'+gcp_zone+'_'+cluster_name
k8s_config = pulumi.Output.all(gke_masterAuth,gke_endpoint,gke_context).apply(lambda args: generate_k8_config(*args))
cluster_provider = pulumi_kubernetes.Provider(cluster_name, kubeconfig=k8s_config)
ns = Namespace(cluster_name, __opts__=ResourceOptions(provider=cluster_provider))
gke_deployment = Deployment(
app_name,
metadata={
'namespace': ns,
'labels': app_label,
},
spec={
'replicas': 3,
'selector':{'matchLabels': app_label},
'template':{
'metadata':{'labels': app_label},
'spec':{
'containers':[
{
'name': app_name,
'image': docker_image,
'ports':[{'name': 'port-5000', 'container_port': 5000}]
}
]
}
}
},
__opts__=ResourceOptions(provider=cluster_provider)
)
deploy_name = gke_deployment
gke_service = Service(
app_name,
metadata={
'namespace': ns,
'labels': app_label,
},
spec={
'type': "LoadBalancer",
'ports': [{'port': 80, 'target_port': 5000}],
'selector': app_label,
},
__opts__=ResourceOptions(provider=cluster_provider)
)
pulumi.export("kubeconfig", k8s_config)
pulumi.export("app_endpoint_ip", gke_service.status['load_balancer']['ingress'][0]['ip'])
__main__.py
ファイルのコンテンツは、パイプラインからデプロイする GKE クラスタとインフラストラクチャを指定します。この Pulumi アプリケーションは、Docker コンテナを介してポッドでアプリケーションを実行する 3 つのノードの Kubernetes クラスタを作成します。このコードは、さまざまなコンピュートノードのアクティブな Docker コンテナにトラフィックを均等にルーティングするロードバランサーリソースも作成します。Pulumi Python アプリの詳細については、このサイトをご覧ください。
Google Cloud のセットアップ
このセクションでは、必要となる GCP 認証情報を作成します。これらの認証情報により、CI/CD パイプラインと Pulumi コードに、GCP でコマンドを実行する権限が付与されます。
GCP プロジェクトの作成
新しいアカウントに対して、デフォルトのプロジェクトが設定されます。後で簡単にティアダウンできるように、新しいプロジェクトを作成して個別に管理しておくことをお勧めします。プロジェクトを作成した後は、プロジェクト ID を必ずコピーしておきます(この ID は、プロジェクト名とは異なります)。
プロジェクト認証情報の取得
次に、Pulumi が GCP プロジェクトのリソースを作成および管理するために使用するサービスアカウントキーを設定します。[サービスアカウントキーの作成] ページに移動します。デフォルトのサービスアカウントを選択するか、新しいサービスアカウントを作成し、[キーのタイプ] として [JSON] を選択して、[作成] をクリックします。この .json
ファイルを pulumi/gke
フォルダに保存します。
セキュリティ上の重要な注意: ファイル名を cicd_demo_gcp_creds.json
に変更し、Google Cloud の認証情報が GitHub パブリックリポジトリに公開されないようにします。さらに、このプロジェクトの .gitignore
ファイルに認証情報の .json
ファイル名を追加できます。このファイルのデータには非常に慎重に扱ってください。このファイルデータが公開されると、第三者がこの情報を使用してアカウントにログインしてリソースを作成することが可能となり、Google Cloud アカウントの請求額が増大する恐れがあります。
CircleCI のセットアップ
次に、Pulumi を CI/CD パイプラインに統合するために CircleCI とパイプラインコンフィグファイルを構成する必要があります。
Google サービスアカウントファイルのエンコード
サービスアカウントファイルを base64
でエンコードしたデータを CircleCI の環境変数として保存する必要があります。ターミナルで次のコマンドを実行して値をエンコードし、結果を取得します。
base64 cicd_demo_gcp_creds.json
このコマンドの結果は次のようになります。
ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAiY2ljZC13b3Jrc2hvcHMiLAogICJwcml2YXRlX2tleV9pZCI6ICJiYTFmZDAwOThkNTE1ZTE0NzE3ZjE4NTVlOTY1NmViMTUwNDM4YTQ4IiwKICAicHJpdmF0ZV9rZXkiOiAiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tXG5NSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUURjT1JuRXFla3F4WUlTXG5UcHFlYkxUbWdWT3VzQkY5NTE1YkhmYWNCVlcyZ2lYWjNQeFFBMFlhK2RrYjdOTFRXV1ZoRDZzcFFwWDBxY2l6XG5GdjFZekRJbXkxMCtHYnlUNWFNV2RjTWw3ZlI2TmhZay9FeXEwNlc3U0FhV0ZnSlJkZjU4U0xWcC8yS1pBbjZ6XG5BTVdHZjM5RWxSNlhDaENmZUNNWXorQmlZd29ya3Nob3BzLmlhbS5nc2VydmljZWFjY291bnQuY29tIgp9Cg==
次のセクションで使用するため、結果をクリップボードにコピーします。
プロジェクト変数の作成
この CI/CD パイプラインが GCP でコマンドを実行するためには、CircleCI でプロジェクトレベルの環境変数を構成する必要があります。
CircleCI ダッシュボードを使用して、次のプロジェクトレベルの環境変数を作成します。
- $DOCKER_LOGIN = Docker Hub のユーザー名
- $DOCKER_PWD = Docker Hubのパスワード
- $GOOGLE_CLOUD_KEYS = 前のセクションで base64 でエンコードした結果
- $PULUMI_ACCESS_TOKEN = Pulumi ダッシュボードからアクセストークンを生成します。
Pulumi を統合した CI/CD パイプライン
これで、Pulumi アプリを CircleCI の config.yml
ファイルに統合するために必要なすべての要素を準備できました。config.yml
を編集し、次のコンフィグをファイルに貼り付けます。この config.yaml
の内容は、デモとプレゼンテーションで使用するサンプルの Python プロジェクトに固有であるため、お使いのプロジェクトの config.yml
とは異なります。詳細なサンプルプロジェクトについては、こちらの GitHub レポジトリを参照してください。どのような処理が行われているのかが明確になるように、このコンフィグサンプルの Pulumi 統合の重要部分について、詳しく説明します。
version: 2.1
orbs:
pulumi: pulumi/pulumi@1.0.1
jobs:
build_test:
docker:
- image: circleci/python:3.7.2
environment:
PIPENV_VENV_IN_PROJECT: 'true'
steps:
- checkout
- run:
name: Install Python Dependencies
command: |
pipenv install --skip-lock
- run:
name: Run Tests
command: |
pipenv run pytest
build_push_image:
docker:
- image: circleci/python:3.7.2
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build and push Docker image
command: |
pipenv install --skip-lock
pipenv run pyinstaller -F hello_world.py
echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
echo 'export IMAGE_NAME=orb-pulumi-gcp' >> $BASH_ENV
source $BASH_ENV
docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
docker push $DOCKER_LOGIN/$IMAGE_NAME
deploy_to_gcp:
docker:
- image: circleci/python:3.7.2
environment:
CLOUDSDK_PYTHON: '/usr/bin/python2.7'
GOOGLE_SDK_PATH: '~/google-cloud-sdk/'
steps:
- checkout
- pulumi/login:
access-token: ${PULUMI_ACCESS_TOKEN}
- run:
name: Install dependecies
command: |
cd ~/
sudo pip install --upgrade pip==18.0 && pip install --user pulumi pulumi-gcp pulumi-kubernetes
curl -o gcp-cli.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz
tar -xzvf gcp-cli.tar.gz
echo ${GOOGLE_CLOUD_KEYS} | base64 --decode --ignore-garbage > ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
./google-cloud-sdk/install.sh --quiet
echo 'export PATH=$PATH:~/google-cloud-sdk/bin' >> $BASH_ENV
source $BASH_ENV
gcloud auth activate-service-account --key-file ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
- pulumi/update:
stack: k8s
working_directory: ${HOME}/project/pulumi/gcp/gke/
workflows:
build_test_deploy:
jobs:
- build_test
- build_push_image:
requires:
- build_test
- deploy_to_gcp:
requires:
- build_push_image
次のコードスニペットは、Pulumi Orb がこのパイプラインで使用されることを指定します。
version: 2.1
orbs:
pulumi: pulumi/pulumi@1.0.1
サンプルパイプラインの jobs:
キーには、3 つの個別のジョブが定義されています。
-
build_test
: このジョブは、アプリケーションの単体テストを実行します。 -
build_push_image
: このジョブは、通常、プロジェクトリポジトリに共存する Dockerfile に基づいて新しい Docker イメージをビルドします。 -
deploy_to_gcp
: このジョブは、Pulumi Orb を介して GKE クラスタにデプロイします。
ここでは、build_push_image
および deploy_to_gcp
ジョブについて詳しく見ていきましょう。
build_push_image:
build_push_image:
docker:
- image: circleci/python:3.7.2
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build and push Docker image
command: |
pipenv install --skip-lock
pipenv run pyinstaller -F hello_world.py
echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
echo 'export IMAGE_NAME=orb-pulumi-gcp' >> $BASH_ENV
source $BASH_ENV
docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
docker push $DOCKER_LOGIN/$IMAGE_NAME
アプリケーションがテストされ正しくパスすると、build_push_image
ジョブにより、このアプリケーションが単一の実行可能バイナリにパッケージングされます。次に、プロジェクトのリポジトリにある Dockerfile
に基づいて、新しい Docker イメージをビルドする docker build
コマンドを実行します。このジョブは既存の環境変数も使用しますが、一意の Docker イメージ名を指定するために使用されるいくつかの新しい環境変数も定義します。このプロジェクトの Dockerfile
を以下に示します。
FROM python:3.7.2
RUN mkdir /opt/hello_world/
WORKDIR /opt/hello_world/
COPY dist/hello_world /opt/hello_world/
EXPOSE 80
CMD [ "./hello_world" ]
docker push
コマンドにより、新しくビルドされた Docker イメージが Docker Hub にアップロードおよび保管され、今後取得できるようになります。
deploy_to_gcp:
deploy_to_gcp:
docker:
- image: circleci/python:3.7.2
environment:
CLOUDSDK_PYTHON: '/usr/bin/python2.7'
GOOGLE_SDK_PATH: '~/google-cloud-sdk/'
steps:
- checkout
- pulumi/login:
access-token: ${PULUMI_ACCESS_TOKEN}
- run:
name: Install dependencies
command: |
cd ~/
sudo pip install --upgrade pip==18.0 && pip install --user pulumi pulumi-gcp pulumi-kubernetes
curl -o gcp-cli.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz
tar -xzvf gcp-cli.tar.gz
echo ${GOOGLE_CLOUD_KEYS} | base64 --decode --ignore-garbage > ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
./google-cloud-sdk/install.sh --quiet
echo 'export PATH=$PATH:~/google-cloud-sdk/bin' >> $BASH_ENV
source $BASH_ENV
gcloud auth activate-service-account --key-file ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
- pulumi/update:
stack: k8s
working_directory: ${HOME}/project/pulumi/gcp/gke/
上記で指定した deploy_to_gcp:
ジョブは、Pulumi アプリと Orb を使用し、GCP で新しい GKE クラスタを実際に立ち上げるパイプラインの一部です。以下では、この deploy_to_gcp:
ジョブについて簡単に説明します。
- pulumi/login:
access-token: ${PULUMI_ACCESS_TOKEN}
上記のコードは、Pulumi Orb の login:
コマンドの仕様と実行を示しています。access-token:
パラメーターには、CircleCI ダッシュボードで設定した ${PULUMI_ACCESS_TOKEN}
環境変数が渡されます。
curl -o gcp-cli.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz
tar -xzvf gcp-cli.tar.gz
echo ${GOOGLE_CLOUD_KEYS} | base64 --decode --ignore-garbage > ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
./google-cloud-sdk/install.sh --quiet
echo 'export PATH=$PATH:~/google-cloud-sdk/bin' >> $BASH_ENV
source $BASH_ENV
gcloud auth activate-service-account --key-file ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
上記のコマンドは、Google Cloud SDK をダウンロードしてインストールします。この SDK は、GCP で GKE クラスタを作成および変更するために必要です。最初の 2 行は、SDK をダウンロードして解凍します。echo ${GOOGLE_CLOUD_KEYS} | base64 --decode...
コマンドは、${GOOGLE_CLOUD_KEYS}
環境変数をデコードし、cicd_gcp_creds.json
にデコードしたコンテンツを入力します。このファイルは、Pulumi アプリプロジェクトのディレクトリに存在する必要があります。この特定の run:
ブロックのコマンドの残り部分は、SDK をインストールします。最後の行は、cicd_demo_gcp_creds.json
ファイルから GCP にアクセスするサービスアカウントを許可します。
- pulumi/update:
stack: k8s
working_directory: ${HOME}/project/pulumi/gcp/gke/
上記のコードは、Pulumi Orb の update:
コマンドを使用して、GCP 上の新しい GKE クラスタへのアプリケーションのデプロイを開始します。pulumi/update:
コマンドは、stack:
および working_directory:
パラメーターを表示します。これらのパラメーターは、Pulumi スタックの名前と Pulumi プロジェクトとして初期化されたディレクトリへのファイルパスをそれぞれ示します。お使いのプロジェクトでの working_directory:
は、上記のコードサンプルとは異なります。
結論
このブログでは、Infrastructure as Code(IaC)ソリューションを CI/CD パイプラインに統合する方法について説明しました。また、CI/CD パイプラインで CircleCI Orb テクノロジーを宣言して実行する方法についても説明しました。これらのサンプルを見ると、CI/CD オートメーションによる IaC ソリューションを使用したコードのビルド、テスト、およびデプロイを深く理解できるようになります。
CircleCI Orbs を使用すると、CircleCI コンフィグの記述が簡素化されるため、生産性が向上します。Orbs は共有できるため、作成済みのコマンド、ジョブ、および Executor をコンフィグファイルで使用でき、時間を節約できます。Orbs の使用は CircleCI と GKE のデプロイに限定されません。Orb レジストリで利用可能な Orbs のリストを確認し、クラウドプラットフォーム、プログラミング言語など対応する Orbs を見つけることができます。
詳細なサンプルプロジェクトについては、こちらの GitHub レポジトリを参照してください。