かきた豆も

自分用メモ

Terraform + GCP で複数の Google アカウントを切り替えるのにハマったメモ

これは

  • 久々に GCP を触る気持ちがあったので Terraform でやろうと思った
  • 複数の Google アカウント使ってると認証情報の切り替えがしんどかったので忘れないためにメモ
  • 自分用

結論の結論

  • おとなしくウェブコンソール使ってプロジェクトとサービスアカウントとサービスアカウントキーを作って Terraform に食わせるのが一般的らしい
  • もっといい方法というかベストプラクティスがあるのかもしれない

結論

  • CLOUDSDK_CONFIG で gcloud CLI が使用する設定ファイル等のディレクトリを指定することが出来るので、 ~/.config/gcloud_hoge のようなディレクトリを作った
    • CLOUDSDK_CONFIG=~/.config/gcloud_hoge とした状態で gcloud auth application-default login コマンドを叩くと ~/.config/gcloud_hoge/application_default_credentials.json爆誕する
    • その上で GOOGLE_CREDENTIALS=~/.config/gcloud_hoge/application_default_credentials.json を Terraform に食わせると再認証する必要無く direnv 使って環境変数1行足すだけで切り替えが実現した
  • 事故を起こしたくないのでデフォルトの ~/.config/gcloud は消した
    • ~/.config/gcloud_趣味用~/.config/gcloud_仕事用爆誕した

過程

  • 複数の Google アカウントを運用しているとつらい
  • AWS だと AWS_PROFILE を direnv なりなんなりで AWS CLI の config 名を環境変数として定義しておくとアカウントの切り替えは楽にできる
  • GCP だと CLOUDSDK_ACTIVE_CONFIG_NAME がそれに相当するだろうと思っていたが違った
    • 厳密には gcloud の CLI での操作をするだけであればそれで合っていたが、Terraform のように GCP SDK を利用しているものは違った
  • Terraform のように GCP SDK を利用しているものは gcloud auth application-default login コマンドで生成される ~/.config/gcloud/application_default_credentials.json を参照している
    • これは1環境に1つしか存在することが出来ないので、アカウントの切り替えを行いたいときには gcloud auth application-default login コマンドを用いて再認証する必要がある
    • このコマンドを叩くとブラウザが起動して認証する必要がある
    • アカウント切り替えたい時に毎回コマンド叩くのが非常にめんどくさいと感じた

やりたかったこと

Google Cloud Text-To-Speechで遊ぼうと思ったのがきっかけ。 以下のように、プロジェクトやサービスアカウントの作成から Cloud Text-To-Speech API の有効化までやろうと思っていたが、なかなかめんどくさいことになってしまった。

variable "billing_account_name" {
  type = string
}

variable "gcp_project_name" {
  type = string
}

provider "google" {}

data "google_billing_account" "default" {
  display_name = var.billing_account_name
  open         = true
}

resource "google_project" "default" {
  project_id          = var.gcp_project_name
  name                = var.gcp_project_name
  auto_create_network = false
}

resource "google_service_account" "default" {
  account_id   = "hoge"
  display_name = "HogeHoge"
}

resource "google_service_account_key" "default" {
  service_account_id = google_service_account.default.name
}

# Save the credential json file.
# This process is only run the first time.
resource "local_file" "service_account_key" {
  filename             = "./secrets/credential.json"
  content              = base64decode(google_service_account_key.default.private_key)
  file_permission      = "0600"
  directory_permission = "0755"
}

# Activate Cloud Text-To-Speech Service API
resource "google_project_service" "tts" {
  project = google_project.default.project_id
  service = "texttospeech.googleapis.com"
}

ついでに

必要なロールはありませんということなのでロールを選択せず作成します。

という記述を見たんだけど、「プロジェクトに紐づくサービスアカウントは何も権限を与えられなくても Cloud Text-To-Speech を使える…ってコト!?」という気持ちになった。

参考にしたもの

https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference

christina04.hatenablog.com

future-architect.github.io

日記

約1年ぶりに髪を切った。 北海道は気温がマイナス5℃とかなので非常に寒い、寒いというか痛い。 寒すぎて耳が千切れたかと思った。

CircleCI で Amazon ECR にマルチアーキテクチャコンテナイメージを push するメモ

これは

aws.amazon.com

  • Intel チップ搭載 Macが今後なくなっていくようなので、Amazon ECR のマルチアーキテクチャコンテナイメージを利用したい
  • CircleCI で Docker Image の build も push もしたい

使ったもの

Terraform

$ terraform version
Terraform v1.0.11
on darwin_amd64
+ provider registry.terraform.io/hashicorp/aws v3.66.0

Amazon ECR マルチアーキテクチャコンテナイメージとは

  • 詳しくはここを読む
  • 端的に言うと docker pull 時に、docker manifest を利用して platform.architecture が自身に合うものを参照するらしい

docker manifest から CPU アーキテクチャが合致する docker image を確認し、pull する

実際にやってみた

これに書いてあるチュートリアル的なのをそのまま CircleCI で行えるようにやっていく。

aws.amazon.com

成果物

github.com

ECR リポジトリを作る

resource "aws_ecr_repository" "default" {
  name = "web-server"
}

CircleCI が ECR リポジトリにあれこれ出来るように IAM User を作る

docker manifest create を行うために通常の ECR 操作用の権限に加えて ecr:BatchGetImageecr:GetDownloadUrlForLayer を与えている。

# IAM User used by CircleCI
resource "aws_iam_user" "default" {
  name = "circleci_web-server"
}

resource "aws_iam_policy" "default" {
  name = "circleci_web-server"

  policy = <<-POLICY
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "ecr:GetAuthorizationToken"
                ],
                "Resource": "*"
            },
            {
                "Effect": "Allow",
                "Action": [
                    "ecr:BatchGetImage",
                    "ecr:BatchCheckLayerAvailability",
                    "ecr:CompleteLayerUpload",
                    "ecr:GetDownloadUrlForLayer",
                    "ecr:InitiateLayerUpload",
                    "ecr:PutImage",
                    "ecr:UploadLayerPart"
                ],
                "Resource": "${aws_ecr_repository.default.arn}"
            }
        ]
    }
    POLICY
}

resource "aws_iam_user_policy_attachment" "default" {
  user       = aws_iam_user.default.name
  policy_arn = aws_iam_policy.default.arn
}

作成した IAM User のアクセスキーを CircleCI に食わせる

AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_ECR_ACCOUNT_URLを与えた。 AWS_ECR_ACCOUNT_URL は ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com だが CircleCI の config の中で組み立てるのがめんどくさい(AWS_ACCOUNT_ID を渡すことになる)のでそのまま URL を渡した。

CircleCI に build と push をさせる

完成形はこれ

github.com

docker build と ECR への docker push はめんどくさいので Orb を使う。

circleci.com

その際、以下のように executor で resource_class を arm.medium にすると arm アーキテクチャを使用することが出来る。

executors:
  amd:
    machine:
      image: 'ubuntu-2004:202101-01'
    resource_class: medium
  arm:
    machine:
      image: 'ubuntu-2004:202101-01'
    resource_class: arm.medium
jobs:
  build-amd64:
    executor: amd
    steps:
      - aws-ecr/build-and-push-image:
          <<: *params
          tag: amd64
  build-arm64:
    executor: arm
    steps:
      - aws-ecr/build-and-push-image:
          <<: *params
          tag: arm64

これで、web-server:amd64web-server:arm64 の2つのイメージが push された。

CircleCI に docker manifest を作らせる

以下のようなジョブを書いた。 circleci/aws-ecr はどれだけググっても(2021年11月末頃調べ) aws-ecr/build-and-push-image の使用方法しか情報が転がっていないが、 aws-ecr/ecr-login のように書くと一部のコマンドだけ使用することも出来る。

  create-manifest:
    executor: amd
    steps:
      - aws-ecr/ecr-login
      - run:
          name: Create docker manifest
          command: >-
              docker manifest create $AWS_ECR_ACCOUNT_URL/web-server \
                $AWS_ECR_ACCOUNT_URL/web-server:amd64 \
                $AWS_ECR_ACCOUNT_URL/web-server:arm64
      - run:
          name: Push docker manifest
          command: docker manifest push $AWS_ECR_ACCOUNT_URL/web-server

このジョブが実行されると、以下のように push した覚えのない latest タグが急に生えてくる。

f:id:kakitamama:20211207100913p:plain

これを pull して manifest を確認すると以下のようになる。

$ docker manifest inspect 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/web-server:latest
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:b6da94ded5a0f9f86f4c03c69ddc675622b133441f3125ac03cb5531d0009e08",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:068c08e45b5a8c326cb985282090706df66fe8afbc5446b9ba110040a7086f64",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      }
   ]
}

多分 latest の manifest から各 architecture の digest を用いてイメージを参照しているのでしょう。

Intel チップ搭載の Mac で arm64 向けビルドしたやつを動かしてみた

WARNING は出るけどなんか動いた。

$ docker run -p 3000:3000 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/web-server:arm64
WARNING: The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64) and no specific platform was requested
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.glob..func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :3000

終。

課題

  • M1 Mac を持っていないのでちゃんとした動作確認が出来ていない。