TerraformでGoogleAPPEngineを構築してみた
Terraformシリーズもいよいよ今回で最後かなぁと思っています。
前回に引き続いてPaaSの構築です。既にAlibabaに追い抜かれてはいますが、三大クラウドの一角のGCPです。GoogleAppEngineとDataStoreを使用するアプリを作成し、そいつを展開するところをTerraformでやってみようというわけです。
まぁ↓こんな感じの構成です。
サンプルアプリはこちらに保存しています。Azureの時と同様にまずはCloudSDKで試してみてからTerraformにてやってみます。またしてもtfファイルはGitHubには上げていないという、、、。いい加減にGitに上げる様にした方がいいんだろうなぁと思うんですが後で見返す際にやりやすいのでこうしています。
カスタムドメインに関してはどうしても手動の部分があるので切り分けて共通項目としてくくりだしているのでTerraform化はしていません。カスタムドメインと無償SSLの箇所については他にいいサイトがあるのでそちらを参照いただいた方がいいかもしれないです。
◆設定作業編
1.CloudSDK版
# ソースコードのコピーとライブラリインストール $ cd workdir $ git clone https://github.com/Otazoman/GAEWebAppSample.git $ cd GAEWebAppSample $ mkdir lib $ pip install --upgrade pip $ pip install -t lib -r requirements.txt #GCPプロジェクト作成 $ gcloud projects create ${project_name} --folder=${folder_id} #GAE作成 $ gcloud app create --project=${project_name} Please choose the region where you want your App Engine application located: [1] asia-east2 (supports standard and flexible) [2] asia-northeast1 (supports standard and flexible) [3] asia-northeast2 (supports standard and flexible) [4] asia-northeast3 (supports standard and flexible) ~略~ [20] cancel Please enter your numeric choice: 2 # プロジェクトと請求先アカウントのリンク $ gcloud beta billing projects link ${project_name} --billing-account ${billing_id} # デプロイ $ gcloud app deploy --project=${project_name} Do you want to continue (Y/n)? Y ※初回はかなり時間がかかる # プロジェクト削除 $ gcloud projects delete gaeappsample-20200816 Do you want to continue (Y/n)? Y
2.Terraform版
2.1.事前準備作業
# ディレクトリ準備 $ mkdir gaeterraform $ cd gaeterraform $ mkdir credentials $ mkdir gae $ mkdir project $ mkdir upload #ソースコピー $ git clone https://github.com/Otazoman/GAEWebAppSample.git src/GAEWebAppSample # 親プロジェクト用シェル作成 $ vi src/make_project.sh $ chmod 755 src/make_project.sh ------------------------------ #/bin/bash #make project gcloud projects create ${pr_project_name} \ --${folder_id} \ --set-as-default #billing account gcloud beta billing projects link ${pr_project_name} \ --billing-account ${billing_id} gcloud config set project ${pr_project_name} #make service account gcloud iam service-accounts create terraform \ --display-name "Terraform admin account" #get credential gcloud iam service-accounts keys create ~/credential.json \ --iam-account ${service_account}@${pr_project_name}.iam.gserviceaccount.com #api enabled gcloud services enable cloudresourcemanager.googleapis.com gcloud services enable cloudbilling.googleapis.com gcloud services enable iam.googleapis.com gcloud services enable serviceusage.googleapis.com gcloud services enable appengine.googleapis.com gcloud services enable storage-component.googleapis.com gcloud services enable storage.googleapis.com gcloud services enable storagetransfer.googleapis.com gcloud services enable datastore.googleapis.com #orgnize and billing gcloud organizations add-iam-policy-binding ${org_id} \ --member serviceAccount:${service_account}@${pr_project_name}.iam.gserviceaccount.com \ --role roles/resourcemanager.projectCreator gcloud organizations add-iam-policy-binding ${org_id} \ --member serviceAccount:${service_account}@${pr_project_name}.iam.gserviceaccount.com \ --role roles/billing.user ------------------------------ $ cp src/make_project.sh ./ ※$で始まる個所は個別書替 # 親プロジェクト用Shell実行 ./make_project.sh # Terraformクレデンシャル取得用Shell準備 $ vi src/get_sakey.sh $ chmod 755 src/get_sakey.sh ------------------------------ #/bin/bash if [ -d credentials ]; then rm -Rf credentials fi gcloud iam service-accounts keys create ${credential} \ --iam-account ${service_account}@${project_name}.iam.gserviceaccount.com export -n GOOGLE_APPLICATION_CREDENTIALS export GOOGLE_APPLICATION_CREDENTIALS="${credential}" ------------------------------ $ cp src/get_sakey.sh ./
2.2.Terraformファイル群
(1)ルートに置くもの
# main.tf ----------------- provider "google" { project = var.project_name region = var.region } module "project" { source = "./project" project_name = var.project_name service_account = var.service_account org_id = var.org_id folder_id = var.folder_id billing_account = var.billing_account } data "template_file" "makeshell" { template = file("${path.module}/get_sakey.sh") vars = { project_name = var.project_name service_account = var.service_account credential = var.credential } } resource "local_file" "get_sakey" { content = data.template_file.makeshell.rendered filename = "${path.module}/get_sakey.sh" provisioner "local-exec" { command = "/bin/bash ${path.module}/get_sakey.sh" } depends_on = [module.project] } module "gae" { source = "./gae" project_name = "${module.project.project_id}" project_id = "${module.project.project_id}" region = var.region credential = var.credential } ----------------- # variable.tf ----------------- variable "admin_user" { default = "your@example.com" } variable "project_name" { default = "project_name" } variable "project_id" { default = "project_id" } variable "org_id" { default = "org_id" } variable "folder_id" { default = "folder_id" } variable "billing_account" { default = "billing_id" } variable "service_account" { default = "terraform" } variable "credential" { default = "/home/user/gaeterraform/credentials/account.json" } variable "region" { default = "asia-northeast1" } -----------------
(2)projectディレクトリ配下
# main.tf ----------------- resource "google_project" "project" { name = var.project_name project_id = var.project_name #org_id = var.org_id folder_id = var.folder_id billing_account = var.billing_account } resource "google_service_account" "sa" { account_id = var.service_account display_name = "Project Service Account" depends_on = [google_project.project] } data "google_iam_policy" "sa-iam-policy" { binding { role = "roles/owner" members = [ "serviceAccount:${google_service_account.sa.email}", ] } } resource "google_service_account_iam_policy" "sa-iam" { service_account_id = google_service_account.sa.id policy_data = data.google_iam_policy.sa-iam-policy.policy_data } resource "google_project_service" "services" { project = google_project.project.project_id for_each = toset(var.services) service = each.value depends_on = [google_project.project] } ----------------- # variable.tf ----------------- variable "project_name" {} variable "billing_account" {} variable "org_id" {} variable "folder_id" {} variable "service_account" {} variable "services" { default = [ "appengine.googleapis.com", "storage-component.googleapis.com", "storage.googleapis.com", "storagetransfer.googleapis.com", "datastore.googleapis.com" ] } ----------------- # outputs.tf ----------------- output "project_id" { value = google_project.project.project_id } -----------------
(3)gaeディレクトリ配下
# main.tf ----------------- data "archive_file" "app-engine-source-zip" { type = "zip" source_dir = var.source_dir output_path = var.output_path } resource "google_storage_bucket" "deployment_config" { bucket_policy_only = true force_destroy = true name = "deployment-config-${var.project_id}" requester_pays = false storage_class = "STANDARD" } resource "google_storage_bucket_object" "app-engine-source-zip" { source = var.source_file bucket = google_storage_bucket.deployment_config.name name = "app-engine-source-zip" depends_on = [data.archive_file.app-engine-source-zip] } resource "google_app_engine_application" "app" { timeouts { create = "15m" update = "15m" } project = var.project_id location_id = var.region } resource "google_app_engine_standard_app_version" "app-version" { timeouts { create = "15m" delete = "15m" } version_id = data.archive_file.app-engine-source-zip.output_md5 service = var.service runtime = var.runtime deployment { zip { source_url = "https://storage.googleapis.com/${google_storage_bucket.deployment_config.name}/${google_storage_bucket_object.app-engine-source-zip.name}" } } entrypoint { shell = var.shell } noop_on_destroy = true depends_on = [google_storage_bucket_object.app-engine-source-zip] } ----------------- # variable.tf ----------------- variable "project_id" {} variable "project_name" {} variable "region" {} variable "credential" { default = "/home/user/gaeterraform/credentials/account.json" } variable "source_file" { default = "/home/user/gaeterraform/upload/gaewebsample.zip" } variable "source_dir" { default = "/home/user/gaeterraform/src/GAEWebAppSample" } variable "output_path" { default = "/home/user/gaeterraform/upload/gaewebsample.zip" } variable "service" { default = "default" } variable "runtime" { default = "python37" } variable "shell" { default = "gunicorn -b :$PORT main:app" } -----------------
2.3.ディレクトリ構成
. |-- credentials | `-- account.json |-- gae | |-- main.tf | `-- variable.tf |-- get_sakey.sh |-- main.tf |-- project | |-- main.tf | |-- make_project.sh | |-- outputs.tf | `-- variable.tf |-- src | |-- GAEWebAppSample | | |-- README.md | | |-- app | | | |-- __init__.py | | | |-- __pycache__ | | | | |-- __init__.cpython-37.pyc | | | | |-- app.cpython-37.pyc | | | | |-- datastore_crud.cpython-37.pyc | | | | `-- viewrender.cpython-37.pyc | | | |-- app.py | | | `-- templates | | | |-- error.html | | | |-- index.html | | | |-- select.html | | | `-- upload.html | | |-- app.yaml | | |-- appengine_config.py | | |-- controllers | | | |-- __init__.py | | | |-- __pycache__ | | | | `-- viewrender.cpython-37.pyc | | | `-- viewrender.py | | |-- main.py | | |-- models | | | |-- __init__.py | | | |-- __pycache__ | | | | |-- datastore.cpython-37.pyc | | | | `-- datastore_crud.cpython-37.pyc | | | |-- datastore.py | | | `-- datastore_crud.py | | `-- requirements.txt | |-- get_sakey.sh |-- upload `-- variable.tf
2.4.Terraformコマンド
$ terraform init $ terraform plan $ terraform apply $ terraform destroy
3.カスタムドメイン追加
3.1.DNSにサブドメイン追加
# プロジェクト切替 $ gcloud config set project ${project_name} Updated property [core/project]. $ gcloud config configurations list | grep True | awk '{print $4}' # コマンドラインからサブドメイン作成 $ gcloud dns managed-zones create gaeappsample \ --description="gae web apps" \ --dns-name=gae.example.com \ --visibility=public # NSレコードを取得する。 $ gcloud dns record-sets list --zone=gaeappsample --name="gae.example.com." --type="NS"*上記で取得したレコードをメインドメイン管理のDNSへ追加する。
3.2.ドメインマッピング
(1) GAEの設定からサブドメインとマッピングする
・「使用するドメインを選択する」から[新しいドメインの所有権を証明]を選択して利用するサブドメインを入力して[所有権を証明]をクリックする。
・ウェブマスターセントラルの画面が開くのでTXTレコードを取得する。[確認方法]で[ドメイン名プロバイダ]を選択して[Google Domains]を選択して赤枠で囲んだ箇所をDNSのTXTレコードに貼り付ける
※自分の場合はGCPのCloudDNSにサブドメインを設定したので、CloudDNSにてレコードを追加した。
・TXTレコードを登録後、10分ほどしてから[確認]をクリックすると確認完了画面が表示される。
・所有権が確認できたら確認済み表示となる
・[続行]をクリックする。
・ドメインのマッピング項目でマッピングされている内容を参照し[マッピングを保存]をクリックする。
・マッピングが正常に完了して緑チェック表示になったら[続行]をクリックする。
・DNSに設定するAレコードとAAAAレコード、CNAMEが表示される。
(2)マッピング保存後に表示されたAレコードとAAAAレコードとCNAMEをDNSに追加する
$ gcloud dns record-sets transaction start --zone="gaeappsample" $ gcloud dns record-sets transaction add --name="www.gae.example.com." --ttl=300 --type=CNAME --zone="gaeappsample" "XXXX.com." $ gcloud dns record-sets transaction add \ "IPv4addr_1" "IPv4addr_2" "IPv4addr_3" "IPv4addr_4" \ --name=gae.example.com. --ttl=300 --type=A --zone="gaeappsample" $ gcloud dns record-sets transaction add \ "Ipv6addr_1" "Ipv6addr_2" "Ipv6addr_3" "Ipv6addr_4" \ --name=gae.example.com. --ttl=300 --type=AAAA --zone="gaeappsample" $ gcloud dns record-sets transaction execute --zone="gaeappsample"
(3)登録が完了するとカスタムドメインの箇所にDNSの登録内容が表示される
◆参考サイト
・GAE入門
https://qiita.com/so-heee/items/2fbf523395889fad2887
https://www.topgate.co.jp/gcp06-how-to-use-cloud-datastore-gae
https://cloud.google.com/appengine/docs/standard/python3/using-cloud-datastore?hl=ja
https://note.com/10mohi6/n/n15cdabfe792d
https://dev.classmethod.jp/articles/gae-webapp/
https://cloud.google.com/appengine/docs/flexible/python/testing-and-deploying-your-app?hl=ja
https://qiita.com/leo1109/items/285b898ecf73621cbc17
https://cloud-textbook.com/2807/
https://www.serversus.work/topics/vyly8dwer5uql5ra5xdg/
https://note.com/yamaken0107/n/n1d19a988dfdb
http://westplain.sakuraweb.com/translate/GAE/Pricing-and-Quotas/Quotas.cgi
https://blog.hashihei.com/2020/03/08/google-app-engine%E3%81%A7paas%E3%81%AB%E5%85%A5%E9%96%80/
https://cloud.google.com/appengine/docs/standard/python3?hl=ja
https://mmtomitomimm.blogspot.com/2018/10/app-engine-cloud-datastore.html
https://codezine.jp/article/detail/5023
https://medium.com/@timakin/mercari-datastore%E5%AE%9F%E6%88%A6%E6%8A%95%E5%85%A5-a7211c56b77a
・DataStoreEmulator
https://blog-hello-world.web.app/posts/2020-01-12-cloud-datastore-local-docker/
https://qiita.com/ideodora/items/a292953a80940ea06d11
https://sanshiro.dev/article/5714489739575296
https://cloud.google.com/datastore/docs/tools/datastore-emulator?hl=ja
https://github.com/googleapis/google-cloud-python/issues/9097
https://blog.sky-net.pw/article/124
https://qiita.com/komtaki/items/550f02b1eda99a27ccbf
https://decoch.hatenablog.com/entry/2019/06/26/174306
https://engineer.crowdworks.jp/entry/2019/03/08/161559
・DataStore
https://qiita.com/tfuruya/items/fa529c6cf54adaa81aa7
https://qiita.com/miyuuuu/items/c9846ccdad9aee7c733c
https://qiita.com/Mahito/items/7578fe7b7a276a597784
https://techbooster.org/gae/15069/
https://www.366service.com/jp/qa/235f3b7802122f6c099e6c6944ea6ebb
https://nansystem.com/python-with-cloud-firestore/
https://komiyak.hatenablog.jp/entry/20170427/1493273145
https://cloud.google.com/datastore/docs/concepts/entities
https://www.the-swamp.info/blog/search-google-cloud-platform-cloud-datastore/
https://googleapis.dev/python/datastore/latest/queries.html
http://goslides.knightso.co.jp/2016/dssearch.slide#15
・GAE独自ドメイン設定
https://www.apps-gcp.com/gae-apply-customdomain-and-ssl-setting/#GAE-3
https://qiita.com/okaji00/items/d0457110169910e92f24
https://blog.kakakikikeke.com/2019/02/how-to-set-custom-domain-on-gae.html
https://cloud-textbook.com/2713/
https://cloud.google.com/appengine/docs/standard/python3/mapping-custom-domains?hl=ja
・DNSコマンド関連
https://cloud.google.com/dns/docs/zones?hl=ja
https://cloud.google.com/dns/records?hl=ja
https://apps-gcp.com/google-cloud-dns-api/
https://heartbeats.jp/hbblog/2014/06/google-cloud-dns.html
https://cloud.google.com/sdk/gcloud/reference/dns/record-sets/transaction/add?hl=ja
https://cloud.google.com/sdk/gcloud/reference/dns/record-sets/transaction?hl=ja
https://stackoverflow.com/questions/30670661/google-cloud-dns-cname-creation-example
https://cloud.google.com/dns/docs/records
https://pekeq.hateblo.jp/entry/2019/11/18/120442
・Terraform関連
https://www.terraform.io/docs/providers/google/r/app_engine_flexible_app_version.html
https://www.terraform.io/docs/providers/google/guides/getting_started.html
https://www.devsamurai.com/ja/gcp-terraform-service-account-permission/
https://apps-gcp-tokyo-02.appspot.com/terraformit-gcp/
https://qiita.com/donko_/items/6289bb31fecfce2cda79
https://gist.github.com/MisaKondo/cb46b0ecd106e9c824a641b14954b8e1
https://www.terraform.io/docs/providers/google/r/app_engine_application.html
https://gb-j.com/column/terraform_gcp/
https://www.devsamurai.com/ja/gcp-terraform-101/
https://qiita.com/sky0621/items/c488e8adb2a51870ad22
https://tech.visasq.com/restart-gcp-infrastructure-as-code1/
https://www.1915keke.com/entry/2018/12/10/145653
https://medium.com/slalom-technology/a-complete-gcp-environment-with-terraform-c087190366f0
https://blog.ri52dksla.dev/posts/gcp-terraform-cloud-run-and-gcs/
https://qiita.com/takat0-h0rikosh1/items/f821fe4e308226123bac
https://www.terraform.io/docs/providers/google/r/google_service_account.html
https://www.terraform.io/docs/providers/google/r/app_engine_domain_mapping.html
https://www.terraform.io/docs/providers/google/r/google_project.html
https://www.terraform.io/docs/providers/google/r/google_service_account_iam.html
https://www.terraform.io/docs/providers/google/r/google_project_service.html
https://pawel.ie/gcp/gcloud/terraform/2020/03/22/GCP-Error-setting-billing-account.html
コメント