このシリーズでは、コードとしてのインスフラストラクチャ (IaC) を導入する方法について説明していきます。開発者の皆さんに IaC のコンセプトをしっかりご理解いただけるよう、チュートリアルやコード サンプルを交えて作成しました。各記事では以下のトピックについて取り上げます。
- パート 1: Kubernetes クラスタを作成する
- パート 2: Docker イメージを作成して Kubernetes にデプロイする
- パート 3: CI/CD によってデプロイを自動化する
今回の記事では、Terraform の IaC デプロイを自動化する継続的インテグレーション & 継続的デプロイメント (CI/CD) のパイプラインの作成方法について説明します。IaC デプロイについては、本シリーズのパート 1 とパート 2 で詳しく説明しています。この記事では以下の手順を行います。
- プロジェクトで使用する CircleCI .config.yml ファイルを新規作成する
- 新しいジョブとワークフローを構成する
- Terraform コードの実行を自動化して、Google Kubernetes Engine (GKE) クラスタを作成し、アプリケーションをデプロイする
事前にパート 1 の前提条件セクションに記載されている手順をすべて完了する必要があります。前提条件の手順が完了したら、こちらのコードリポジトリに含まれる CircleCI .config.yml ファイルから見ていきましょう。
継続的インテグレーションと継続的デプロイメント
開発者やチームは、CI/CD パイプラインを採用することで、ビルドとテストのプロセスを自動化できます。これにより、ソフトウェア開発プロセスのステータスをほぼリアルタイムで把握できる貴重なフィードバック ループが確立されます。また、プロセス実行の一貫性や結果の精度を向上させるだけでなく、プロセスの最適化やスピードアップにも貢献します。CI/CD による開発作業の合理化は、一般的なアプローチとして多くの開発チームに浸透しつつあります。価値の高い CI/CD パイプラインを構築するには、何度も繰り返すタスクをどのように統合、自動化できるのかを理解しておくことが重要です。
パート 1 とパート 2 では、Terraform を使用して新しい GKE クラスタを作成すると共に、アプリケーションをデプロイし、実行し、サービスを提供するための関連 Kubernetes オブジェクトを作成しました。そのための Terraform コマンドは、ターミナルから手動で実行しました。Terraform コードの開発中や変更中は手動でもかまいませんが、Terraform コマンドの実行は自動化したほうが便利です。自動化にはさまざまな方法がありますが、ここでは CI/CD パイプライン内で自動化する方法に注目していきます。
CircleCI パイプライン
CircleCI パイプラインは、プロジェクトで作業をトリガーするときに実行されるプロセス全体を指す言葉です。パイプラインにはワークフローが含まれており、ワークフローはジョブのオーケストレーションを担います。この仕組みはすべてプロジェクトの設定ファイルに定義されています。次のセクションでは、実際に CI/CD パイプラインを定義してプロジェクトを構築していきます。
CircleCI でプロジェクトをセットアップする
このプロジェクトで使用する config.yml
ファイルを作成する前に、このプロジェクトを CircleCI に追加する必要があります。これについては CircleCI の入門ガイドを参照してください。CircleCI のセットアップ セクションに記載された手順を完了できたら、プロジェクトレベルの環境変数を構成します。
プロジェクト レベルの環境変数
このパイプライン内の一部のジョブは、ターゲット サービスでコマンドを実行するために認証情報にアクセスする必要があります。このセクションでは、これらのジョブに必要な認証情報を定義し、プロジェクトレベルの環境変数として CircleCI に入力する方法について説明します。CircleCI ダッシュボードで作成する必要がある環境変数名の一覧を以下にまとめます。この一覧を参考にしながら、Name フィールドに EnVar Name:
の値を、Value フィールドにそれぞれの認証情報を入力してください。
- EnVar Name: TF_CLOUD_TOKEN - Value: Terraform Cloud ユーザー トークンをホスティングするローカルの.terraformrc ファイルの Base64 encoded value
- EnVar Name: DOCKER_LOGIN - Value: Docker Hub ユーザー名
- EnVar Name: DOCKER_PWD - Value: Docker Hub パスワード
- EnVar Name: GOOGLE_CLOUD_KEYS - Value: GCP 認証情報の JSON ファイルの Base64 エンコード値
上記のすべての環境変数を入力できたら、config.yml
ファイル内でパイプラインの構築に取り掛かります。
CircleCI config.yml
config.yml は、CI/CD 関連ジョブの処理と実行について定義するためのファイルです。このセクションでは、パイプラインのジョブとワークフローを定義していきます。エディターで .circleci/.config.yml
ファイルを開き、中身を削除して以下の内容をペーストします。
version: 2.1
jobs:
version:
キーには、このパイプラインの実行中に使用するプラットフォーム機能を指定します。jobs:
キーには、このパイプライン用に定義するジョブの一覧を指定します。パイプライン内で実行するジョブを作成しましょう。
ジョブ - run_tests:
CircleCI のプラットフォームをスムーズに利用できるよう、CircleCI のリファレンス ドキュメントで、特別なキーや機能について確認しておくことをお勧めします。今回取り上げるジョブに含まれているキーについては、以下に簡単にご説明します。
- docker: - ジョブを実行するランタイムを表すキー
- image: - このジョブで使用する Docker コンテナを表すキー
- steps: - ジョブの実行中に実行される実行可能コマンドのリストまたはコレクションを表すキー
- checkout: - ソース コードを構成済みのパスにチェックアウトするために使用する特別なステップのキー
- run: - すべてのコマンドライン プログラムを呼び出すために使用するキー
- store_test_results: - ビルドのテスト結果をアップロードおよび保存するための特別なステップを表すキー
- path: - JUnit XML または Cucumber JSON のテスト メタデータ ファイルが格納されたサブディレクトリを含むディレクトリへのパス (絶対パス、または working_directory からの相対パス)
- store_artifacts: - Web アプリまたは API からアクセスできるアーティファクト (ログ、バイナリなど) を格納するステップを表すキー
- path: - ジョブ アーティファクトの保存に使用するプライマリ コンテナ内のディレクトリのパス
CI/CD のメリットは、新しく記述されたコードのテストを自動で実行できるという点です。コードを変更するたびにテストを実行することで、コード内の既知または未知のバグを検知できるようになります。config.yml
ファイル内に新しいジョブを定義してみましょう。以下の内容をファイルにペーストしてください。コード ブロック内で何が行われるかについて詳しく説明していきます。
run_tests:
docker:
- image: circleci/node:12
steps:
- checkout
- run:
name: Install npm dependencies
command: |
npm install --save
- run:
name: Run Unit Tests
command: |
./node_modules/mocha/bin/mocha test/ --reporter mochawesome --reporter-options reportDir=test-results,reportFilename=test-results
- store_test_results:
path: test-results
- docker: キーと image: キー - このジョブで使用する Executor と Docker イメージを指定する
- command:
npm install --save
キー - アプリ内で使用するアプリ依存関係をインストールする - name: Run Unit Tests - 自動化されたテストを実行し、
test-results/
という名前のローカル ディレクトリに保存する - store_test_results: -
test-results/
ディレクトリに結果を保存し、CircleCI 内のビルドにピン留めする特別なコマンド
このジョブは単体テストとして機能し、コード内のエラーを特定するのに役立ちます。いずれかのテストでエラーが発生すると、パイプライン全体の構築が失敗し、開発者にエラーの修正を求めるメッセージが表示されます。目標となるのは、すべてのテストとジョブをエラーなしで成功させることです。次に、Docker イメージを作成して Docker Hub レジストリにプッシュするジョブを構築します。
ジョブ - build_docker_image
本シリーズのパート 2 では、Docker イメージを手動で作成し、Docker Hub レジストリにプッシュしました。このジョブは、同じことを自動で行います。以下にbuild_docker_image:
ジョブの例を示します。このコードブロックを config.yml
ファイルに追加したうえで、各要素について詳しく見ていきましょう。
build_docker_image:
docker:
- image: circleci/node:12
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build Docker image
command: |
export TAG=0.2.<< pipeline.number >>
export IMAGE_NAME=$CIRCLE_PROJECT_REPONAME
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_docker_image
ジョブはきわめてシンプルなジョブです。皆さんは既に、ここに含まれるほとんどの CircleCI YAML キーを目にしたことがあると思うので、ここでは詳しい説明を省いて name: Build Docker Image
コマンド ブロックに着目していきます。
export TAG=0.2.<< pipeline.number >>
の行では、pipeline.number の値を使用して Dockerタグ値を実行対象のパイプライン番号に関連付けるローカル環境変数を定義しています。export IMAGE_NAME=$CIRCLE_PROJECT_REPONAME
Docker イメージの命名で使用する変数を定義しています。
docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
は、前の手順で設定したプロジェクト レベルの変数と、先ほど指定したローカル環境変数を組み合わせてDocker build コマンドを実行します。
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
では、Docker Hub認証情報を認証してプラットフォームにアクセスします。最後に、docker push $DOCKER_LOGIN/$IMAGE_NAME
では、新しい Docker イメージを Docker Hub レジストリにアップロードします。
パート 2 において手動で実行したコマンドに環境変数を追加しただけなので、この動作については馴染みがあるはずです。次のセクションでは、GKE クラスタを作成する Terraform コードを実行するジョブを構築します。
ジョブ - gke_create_cluster
このジョブでは、part03/iac_gke_cluster/
ディレクトリにある Terraform コードの実行を自動化します。config.yml
ファイルに以下のコードブロックを追加して保存します。
gke_create_cluster:
docker:
- image: ariv3ra/terraform-gcp:latest
environment:
CLOUDSDK_CORE_PROJECT: cicd-workshops
steps:
- checkout
- run:
name: Create GKE Cluster
command: |
echo $TF_CLOUD_TOKEN | base64 -d > $HOME/.terraformrc
echo $GOOGLE_CLOUD_KEYS | base64 -d > $HOME/gcloud_keys
gcloud auth activate-service-account --key-file ${HOME}/gcloud_keys
cd part03/iac_gke_cluster/
terraform init
terraform plan -var credentials=$HOME/gcloud_keys -out=plan.txt
terraform apply plan.txt
このジョブ コードブロックの重要な要素を確認していきましょう。まず注目していただきたいのが、Executor Docker イメージであるimage: ariv3ra/terraform-gcp:latest
です。これは先ほど作成したイメージで、Google SDKと Terraform CLI の両方がインストールされています。このイメージを使用しない場合は、このジョブにインストール ステップを追加する必要があります。すると、ジョブ実行のたびにツールのインストールと構成が行われるようになります。次に注目したいのは、environment: CLOUDSDK_CORE_PROJECT: cicd-workshops
のキーです。後で実行する gcloud cli
コマンドに必要な環境変数の値を設定しています。
echo $TF_CLOUD_TOKEN | base64 -d > $HOME/.terraformrc
は、$TF_CLOUD_TOKEN
値をデコードするコマンドです。Terraform が各 Terraform クラウドワークスペース上のステート データにアクセスするために必要となる ./terraformrc
ファイルを作成します。
echo $GOOGLE_CLOUD_KEYS | base64 -d > $HOME/gcloud_keys
は $GOOGLE_CLOUD_KEYS
値をデコードするコマンドです。glcoud cli
が GCP にアクセスするために使用する gcloud_keys
ファイルを作成します。
gcloud auth activate-service-account --key-file ${HOME}/gcloud_keys
は、先ほどデコードして生成した gcloud_keys
ファイルを使用して GCP へのアクセスを認証するコマンドです。
残りは、terraform cli
コマンドと -var
パラメーターです。Terraform の variables.tf
ファイルに定義された個々の変数の default
値を指定したりオーバーライドしたりします。terraform apply plan.txt
を実行すると、このジョブにより新しい GKE クラスタが作成されます。
ジョブ - gke_deploy_app
このジョブでは、part03/iac_kubernetes_app/
ディレクトリにある Terraform コードの実行を自動化します。config.yml ファイルに以下のコードブロックを追加して保存します。
gke_deploy_app:
docker:
- image: ariv3ra/terraform-gcp:latest
environment:
CLOUDSDK_CORE_PROJECT: cicd-workshops
steps:
- checkout
- run:
name: Deploy App to GKE
command: |
export CLUSTER_NAME="cicd-workshops"
export TAG=0.2.<< pipeline.number >>
export DOCKER_IMAGE="docker-image=${DOCKER_LOGIN}/${CIRCLE_PROJECT_REPONAME}:$TAG"
echo $TF_CLOUD_TOKEN | base64 -d > $HOME/.terraformrc
echo $GOOGLE_CLOUD_KEYS | base64 -d > $HOME/gcloud_keys
gcloud auth activate-service-account --key-file ${HOME}/gcloud_keys
gcloud container clusters get-credentials $CLUSTER_NAME --zone="us-east1-d"
cd part03/iac_kubernetes_app
terraform init
terraform plan -var $DOCKER_IMAGE -out=plan.txt
terraform apply plan.txt
export ENDPOINT="$(terraform output endpoint)"
mkdir -p /tmp/gke/ && echo 'export ENDPOINT='${ENDPOINT} > /tmp/gke/gke-endpoint
- persist_to_workspace:
root: /tmp/gke
paths:
- "*"
このジョブ コードブロックの重要な要素と、先ほど取り上げなかった新しい要素について見ていきましょう。
export CLUSTER_NAME="cicd-workshops"
は、デプロイ先の GCP プロジェクトの名前を格納する変数を定義します。
gcloud container clusters get-credentials $CLUSTER_NAME --zone="us-east1-d"
は、先ほどのジョブで作成した GKEクラスタから kubeconfig
データを取得するコマンドです。
terraform plan -var $DOCKER_IMAGE -out=plan.txt
は、Terraform の variables.tf
ファイルに定義された個々の変数の default
値をオーバーライドするコマンドです。
export ENDPOINT="$(terraform output endpoint)"
は、Terraform コマンドで生成された出力 endpoint
値をローカル環境変数に割り当てます。このローカル環境変数はファイルに保存され、CircleCI ワークスペースに保管されます。後でこの値を取得して、アタッチされた CircleCI ワークスペースからアタッチし、必要に応じてフォローアップ ジョブで使用することが可能です。
Job - gke_destroy_cluster
このパイプライン用に作成する最後のジョブです。ここまでの CI/CD ジョブで構築したすべてのリソースとインフラストラクチャを破棄します。destroyコマンドを実行するジョブは、スモークテスト、結合テスト、パフォーマンス テストといったさまざまなテスト形式に使用可能なエフェメラルなリソースに最適で、役目を終えた構成要素を破棄できます。
このジョブでは、part03/iac_kubernetes_app/
ディレクトリにある Terraform コードの実行を自動化します。config.yml ファイルに以下のコードブロックを追加して保存します。
gke_destroy_cluster:
docker:
- image: ariv3ra/terraform-gcp:latest
environment:
CLOUDSDK_CORE_PROJECT: cicd-workshops
steps:
- checkout
- run:
name: Destroy GKE Cluster
command: |
export CLUSTER_NAME="cicd-workshops"
export TAG=0.2.<< pipeline.number >>
export DOCKER_IMAGE="docker-image=${DOCKER_LOGIN}/${CIRCLE_PROJECT_REPONAME}:$TAG"
echo $TF_CLOUD_TOKEN | base64 -d > $HOME/.terraformrc
echo $GOOGLE_CLOUD_KEYS | base64 -d > $HOME/gcloud_keys
gcloud auth activate-service-account --key-file ${HOME}/gcloud_keys
cd part03/iac_kubernetes_app
terraform init
gcloud container clusters get-credentials $CLUSTER_NAME --zone="us-east1-d"
terraform destroy -var $DOCKER_IMAGE --auto-approve
cd ../iac_gke_cluster/
terraform init
terraform destroy -var credentials=$HOME/gcloud_keys --auto-approve
このジョブ コードブロックの重要な要素と、先ほど取り上げなかった新しい要素について見ていきましょう。
terraform destroy -var credentials=$HOME/gcloud_keys --auto-approve
は、part03/iac_gke_cluster
ディレクトリと part03/iac_kubernetes_app/
ディレクトリ内の Terraformコードで作成されたすべてのリソースを破棄する、Terraformの destroy コマンドを実行するコマンドです。
これで、パイプライン内に必要なジョブをすべて定義できました。この後は、パイプライン内でのジョブの実行、処理をオーケストレーションする CircleCI ワークフローを作成します。
CircleCI ワークフロー
このパイプライン内のすべてのジョブを作成できたので、次にジョブの実行方法と処理方法を定義するワークフローを作成します。ワークフローはジョブの指示書のようなものです。個々のジョブをどのタイミングで、どのようにして実行するかを指定します。以下は、パイプライン内で使用するワークフロー ブロックです。以下のワークフロー コードブロックを config.yml
ファイルに追加します。
workflows:
build_test:
jobs:
- run_tests
- build_docker_image
- gke_create_cluster
- gke_deploy_app:
requires:
- run_tests
- build_docker_image
- gke_create_cluster
- approve-destroy:
type: approval
requires:
- gke_create_cluster
- gke_deploy_app
- gke_destroy_cluster:
requires:
- approve-destroy
上記のコードブロックは、パイプラインのワークフローの定義です。このブロックで何が行われるか見ていきましょう。workflows:
キーは、ワークフローの要素を指定します。build_test:
はこのワークフローの名前と識別子の組み合わせです。
jobs:
キーは、config.yml
ファイル内に定義された実行ジョブの一覧です。パイプラインで実行するジョブをここで指定します。今回は以下のジョブが指定されています。
- run_tests
- build_docker_image
- gke_create_cluster
- gke_deploy_app:
requires:
- run_tests
- build_docker_image
- gke_create_cluster
- approve-destroy:
type: approval
requires:
- gke_create_cluster
- gke_deploy_app
- gke_destroy_cluster:
requires:
- approve-destroy
run_tests
、build_docker_image
、gke_create_cluster
のワークフロージョブは、requires:
キーを持つ gke_deploy_app:
アイテムとは異なり、すべて並列実行されます。ジョブは既定で並列に実行されるため、依存関係がある場合は、指定されたジョブを開始する前に完了しておく必要のあるジョブの一覧と共に、requires:
キーを使用し、ジョブ名に基づいて依存関係を明示的に要求する必要があります。requires:
キーは、他のジョブの成功に対する依存関係を構築します。これにより、パイプラインの実行をセグメント化し、制御することができます。
approve-destroy:
アイテムには、手動の承認ステップを必要とするジョブを指定します。ワークフロー ジョブ リスト内の次のジョブの実行に承認が求められる場合、ユーザーの介入が必要になります。次の gke_destroy_cluster:
ジョブは、approval-destroy:
ジョブが完了してから実行され、パイプライン内で以前に実行したジョブによって作成されたすべてのリソースを破棄します。
完成した .config.yml ファイル
以下は完成した config.yml
ファイルの例です。プロジェクト コード レポジトリの .circleci/
ディレクトリにも同じファイルが収められています。
version: 2.1
jobs:
run_tests:
docker:
- image: circleci/node:12
steps:
- checkout
- run:
name: Install npm dependencies
command: |
npm install --save
- run:
name: Run Unit Tests
command: |
./node_modules/mocha/bin/mocha test/ --reporter mochawesome --reporter-options reportDir=test-results,reportFilename=test-results
- store_test_results:
path: test-results
- store_artifacts:
path: test-results
build_docker_image:
docker:
- image: circleci/node:12
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build Docker image
command: |
export TAG=0.2.<< pipeline.number >>
export IMAGE_NAME=$CIRCLE_PROJECT_REPONAME
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
gke_create_cluster:
docker:
- image: ariv3ra/terraform-gcp:latest
environment:
CLOUDSDK_CORE_PROJECT: cicd-workshops
steps:
- checkout
- run:
name: Create GKE Cluster
command: |
echo $TF_CLOUD_TOKEN | base64 -d > $HOME/.terraformrc
echo $GOOGLE_CLOUD_KEYS | base64 -d > $HOME/gcloud_keys
gcloud auth activate-service-account --key-file ${HOME}/gcloud_keys
cd part03/iac_gke_cluster/
terraform init
terraform plan -var credentials=$HOME/gcloud_keys -out=plan.txt
terraform apply plan.txt
gke_deploy_app:
docker:
- image: ariv3ra/terraform-gcp:latest
environment:
CLOUDSDK_CORE_PROJECT: cicd-workshops
steps:
- checkout
- run:
name: Deploy App to GKE
command: |
export CLUSTER_NAME="cicd-workshops"
export TAG=0.2.<< pipeline.number >>
export DOCKER_IMAGE="docker-image=${DOCKER_LOGIN}/${CIRCLE_PROJECT_REPONAME}:$TAG"
echo $TF_CLOUD_TOKEN | base64 -d > $HOME/.terraformrc
echo $GOOGLE_CLOUD_KEYS | base64 -d > $HOME/gcloud_keys
gcloud auth activate-service-account --key-file ${HOME}/gcloud_keys
gcloud container clusters get-credentials $CLUSTER_NAME --zone="us-east1-d"
cd part03/iac_kubernetes_app
terraform init
terraform plan -var $DOCKER_IMAGE -out=plan.txt
terraform apply plan.txt
export ENDPOINT="$(terraform output endpoint)"
mkdir -p /tmp/gke/
echo 'export ENDPOINT='${ENDPOINT} > /tmp/gke/gke-endpoint
- persist_to_workspace:
root: /tmp/gke
paths:
- "*"
gke_destroy_cluster:
docker:
- image: ariv3ra/terraform-gcp:latest
environment:
CLOUDSDK_CORE_PROJECT: cicd-workshops
steps:
- checkout
- run:
name: Destroy GKE Cluster
command: |
export CLUSTER_NAME="cicd-workshops"
export TAG=0.2.<< pipeline.number >>
export DOCKER_IMAGE="docker-image=${DOCKER_LOGIN}/${CIRCLE_PROJECT_REPONAME}:$TAG"
echo $TF_CLOUD_TOKEN | base64 -d > $HOME/.terraformrc
echo $GOOGLE_CLOUD_KEYS | base64 -d > $HOME/gcloud_keys
gcloud auth activate-service-account --key-file ${HOME}/gcloud_keys
cd part03/iac_kubernetes_app
terraform init
gcloud container clusters get-credentials $CLUSTER_NAME --zone="us-east1-d"
terraform destroy -var $DOCKER_IMAGE --auto-approve
cd ../iac_gke_cluster/
terraform init
terraform destroy -var credentials=$HOME/gcloud_keys --auto-approve
workflows:
build_test:
jobs:
- run_tests
- build_docker_image
- gke_create_cluster
- gke_deploy_app:
requires:
- run_tests
- build_docker_image
- gke_create_cluster
- approve-destroy:
type: approval
requires:
- gke_create_cluster
- gke_deploy_app
- gke_destroy_cluster:
requires:
- approve-destroy
まとめ
お疲れさまでした。本シリーズのパート3はこれで以上です。今回は、Terraform を使用して IaC リソースを実行する config.yml
ファイルを新しく作成しました。さらに config.yml
の重要な要素や、CircleCI プラットフォームに関連する内部のコンセプトについても解説しました。
本シリーズでは、Docker、GCP、Kubernetes、Terraform、CircleCIといった、さまざまなコンセプトとテクノロジーについて、具体的な手順を交えながら紹介してきました。複数のプロジェクトをつなぎ合わせて CircleCI を使用する方法や、Terraformコードを活用してターゲットのデプロイ環境でアプリケーションをテストする方法についても取り上げました。本シリーズは、DevOpsの重要なコンセプトやテクノロジー、そしてそれらを組み合わせる方法について理解を深めていただけるように作成されています。
コードの変更、新しいTerraformプロバイダーの追加、CI/CD ジョブやパイプラインの再構成を、ご自身の手でお試しになることをお勧めします。ここで学んだ知識と、頭の中にあるアイデアを組み合わせて、リリースの目標を達成しましょう。ブログ記事を読むだけでなく実際に試してみることで、学びがさらに深まります。
最後までお読みいただきありがとうございました。皆さんのお役に立てたなら幸いです。ご意見やご感想がございましたら、Twitter でお気軽に @punkdata宛にメンションしてください。
さらに詳しく知りたい方は以下の資料を参照してください。