疲れた心が満たされたので、寿司と牡蠣と日本酒は偉大。救いは酒だけかもしれない。
Cloud SQLでPostgreSQLを使っていたのですが、諸事情でSpannerを検証する必要があるので、ざっくり構築して接続確認するまでのメモです。
Terraformでざっくり作ります。マルチリージョン構成で、スペックは最低で作ります。 database_dialectをPOSTGRESQLにすることでPostgreSQLと互換が取れます。
########################################### # Service API ########################################### resource "google_project_service" "main" { for_each = { for service in [ "spanner.googleapis.com", ] : service => service } service = each.key disable_dependent_services = true disable_on_destroy = false } ########################################### # Spanner ########################################### resource "google_spanner_instance" "main" { config = "asia1" name = "poc-spanner" display_name = "poc-spanner" processing_units = 100 depends_on = [ google_project_service.main ] } resource "google_spanner_database" "database" { instance = google_spanner_instance.main.name name = "poc" database_dialect = "POSTGRESQL" }
SpannerにはPostgreSQLのインターフェースがあるが、これに接続するために、PGAdapterを使用する必要があります。 PGAdapterはローカルで起動して、Spannerに接続するためのプロキシとして働きます。
今回はMac上にPGAdapterをインストールして、接続確認します。
とりあえず、javaのランタイムとpsqlクライアントをインストールします。
brew install java echo 'export PATH="/opt/homebrew/opt/openjdk/bin:$PATH"' >> ~/.zshrc sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk brew install libpq echo 'export PATH="/opt/homebrew/opt/libpq/bin:$PATH"' >> ~/.zshrc exec ${SHELL} -l
PGAdapterをダウンロードして、起動します
wget https://storage.googleapis.com/pgadapter-jar-releases/pgadapter.tar.gz && tar -xzvf pgadapter.tar.gz java -jar pgadapter.jar -p ${PROJECT_ID} -i ${INSTANCE_NAME} -d ${DATABASE}
ターミナルを別窓で開いてpsqlで接続して雑にテーブルを作ってみます。
% psql -h localhost psql (16.1, server 14.1) Type "help" for help. poc=> CREATE TABLE Staff ( poc-> id INTEGER NOT NULL, poc(> name TEXT NOT NULL, poc(> age INTEGER , poc(> PRIMARY KEY (id));
気持ちテーブル作成に時間がかかる気がします。
コンソールからテーブルを確認すると作成されていることが確認できます。
いったん接続出来るところまで確認できました。 ここから既存のデータを入れたり、Cloud Run上のアプリケーションから接続したりを確認していきます。
Terraformで以下のようにECSイベントが発生した際にそれをCloudWatch Logsに出力するEventBridge ruleとtargetを作成したところ、うまくCloudWatch Logsにイベントが出力されませんでした。
resource "aws_cloudwatch_log_group" "main" { name = "/aws/events/masasuzu/test/ecs/event" retention_in_days = 3 } module "eventbridge" { source = "terraform-aws-modules/eventbridge/aws" create_bus = false create_role = false rules = { "masasuzu-test-ecs-event-log" = { event_pattern = jsonencode({ "source" : ["aws.ecs"], }) enabled = true } } targets = { "masasuzu-test-ecs-event-log" = [ { name = "masasuzu-test-ecs-event-log" arn = aws_cloudwatch_log_group.main.arn } ] } }
試しにコンソールから同様のリソースを作成したところうまくCloudWatch Logsに出力され、Terraformで作ったEventBridgeも正しく動くようになりました。
ここからわかることはコンソールでEventBridgeの設定をした際に裏側で暗黙的になにかリソースが作られたということです。
EventBridgeからCloudWatch Logsへの出力を許可するためにCloudWatch Logsのリソースポリシーを設定する必要があります。
まっさらなAWSアカウントでは以下のようにCloudWatch Logsのリソースポリシーが設定されています。何も設定されていません。
% aws logs describe-resource-policies --no-cli-pager { "resourcePolicies": [] }
コンソールからEventBridgeの設定をすると以下のような設定が追加されます。これにより、EventBridgeからCloudWatch LogsへのPutLogEventsやCreateLogStreamが許可され、無事イベントがログに出力されるようになります。
% aws logs describe-resource-policies --no-cli-pager { "resourcePolicies": [ { "policyName": "TrustEventsToStoreLogEvents", "policyDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"TrustEventsToStoreLogEvent\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"delivery.logs.amazonaws.com\",\"events.amazonaws.com\"]},\"Action\":[\"logs:CreateLogStream\",\"logs:PutLogEvents\"],\"Resource\":\"arn:aws:logs:ap-northeast-1:xxxxxxxxxx:log-group:/aws/events/*:*\"}]}", "lastUpdatedTime": 1705025982872 } ] }
リソースポリシーを設定するために、以下のような記述を追加してあげると良いでしょう。
data "aws_iam_policy_document" "main" { statement { actions = [ "logs:CreateLogStream", "logs:PutLogEvents", ] resources = ["arn:aws:logs:ap-northeast-1:${var.account_id}:log-group:${var.log_group_path}:*"] principals { identifiers = ["events.amazonaws.com"] type = "Service" } } } resource "aws_cloudwatch_log_resource_policy" "main" { policy_document = data.aws_iam_policy_document.main.json policy_name = "EventsToLog" }
参考: CloudWatch Logs リソースへの許可の管理の概要 - Amazon CloudWatch Logs
参考: aws_cloudwatch_log_resource_policy | Resources | hashicorp/aws | Terraform | Terraform Registry
Google Cloud Storageのバケットのリージョンは作成後に変更できません。 そのため、シングルリージョンで作ったバケットをデュアルリージョンまたはマルチリージョンに変えるためには新規で作成し、オブジェクトを全部新しいバケットにコピーした上で切り替えないといけません。 そのためのオブジェクト同期の方法について考えます。
ドキュメントで紹介されている例は転送ジョブとgcloud storage cp
gsutil cp
ですが、これだとコピーはできますが、バケットの中身を同じにはできません。ここで別解として gsutil rsync
を使います。
コマンド例としては以下の通りになります。
gsutil -m rsync -r -d gs://${転送元バケット名} gs://${転送先バケット名}
The -m option typically will provide a large performance boost if either the source or destination (or both) is a cloud URL. If both source and destination are file URLs the -m option will typically thrash the disk and slow synchronization down.
-mオプションはパフォーマンスの向上のため
The rsync -d option is very useful and commonly used, because it provides a means of making the contents of a destination bucket or directory match those of a source bucket or directory. This is done by copying all data from the source to the destination and deleting all other data in the destination that is not in the source. Please exercise caution when you use this option: It's possible to delete large amounts of data accidentally if, for example, you erroneously reverse source and destination.
-rオプションはディレクトリ再帰
The -R and -r options are synonymous. Causes directories, buckets, and bucket subdirectories to be synchronized recursively. If you neglect to use this option gsutil will make only the top-level directory in the source and destination URLs match, skipping any sub-directories.
-dは削除オプションとなり転送元に存在しないオブジェクトが転送先にある場合削除されてしまいます。これをつけることで転送元と転送先が同期できますが、バケットを間違えると大変なことになるので注意が必要です。
v0からv2にバージョンアップした際にdeploy-cloudrunでlabelを更新する挙動に変わったので、これをやめさせたいといのが趣旨です。 Cloud Run Serviceのlabelはterraform側で制御したいので、GitHub Actions側で変更されると困るのです。
結論から言うと、 skip_default_labelsを trueにしてあげると良いです。
# (省略) - name: Deploy to Cloud Run uses: 'google-github-actions/deploy-cloudrun@v2' with: service: ${{ inputs.SERVICE_NAME }} image: ${{ inputs.REPOSITORY }}:${{ github.sha }} project_id: ${{ inputs.PROJECT_ID }} region: ${{ inputs.REGION }} skip_default_labels: true # <<<<< これ
ドキュメントの該当箇所は以下の通りです。
skip_default_labels: (Optional) Skip applying the special annotation labels that indicate the deployment came from GitHub Actions. The GitHub Action will automatically apply the following labels which Cloud Run uses to enhance the user experience:
managed-by: github-actions commit-sha: <sha>
Setting this to true will skip adding these special labels. The default value is false.
EC2インスタンスのuserdataの実行履歴を見たい - ふり返る暇なんて無いね でログを確認したところ、以下のように言われてしまった。
2023-09-21 02:01:06,809 - __init__.py[WARNING]: Unhandled non-multipart (text/x-not-multipart) userdata: 'b''...'
どうもシェバンがないのでうまくbashスクリプトとして認識してくれなかったようだ。下記1行を先頭に加えて再度起動したところうまく動いた。
#!/bin/bash
参考 起動時に Linux インスタンスでコマンドを実行する - Amazon Elastic Compute Cloud
ユーザーデータシェルスクリプトは、#! 文字と、スクリプトの読み取り先であるインタープリタのパス (通常は /bin/bash)) で開始する必要があります。シェルスクリプティングに関する有用な紹介文は、Linux ドキュメントプロジェクト (tldp.org) の「BASHプログラミングのハウツー」で入手できます。
メモ残しておかないと忘れるので、雑に置いておく。
Session Manager等でログインして、 /var/log/cloud-init-output.log
を覗く。
sudo cat /var/log/cloud-init-output.log
参考: 起動時に Linux インスタンスでコマンドを実行する - Amazon Elastic Compute Cloud
terraformでdatadogプロバイダーを使ってdatadogリソースを管理しようとしていたのですが、apiキーもappキーも正しいものを使ってるはずなのに、403が出てこまりました。
Planning failed. Terraform encountered an error while generating this plan. ╷ │ Error: 403 Forbidden │ │ with module.datadog.provider["registry.terraform.io/datadog/datadog"], │ on modules/datadog/provider.tf line 19, in provider "datadog": │ 19: provider "datadog" { │ ╵
当初こんな感じに設定していました。api_urlの指定をしてませんでした。
provider "datadog" { api_key = var.datadog_api_key app_key = var.datadog_app_key }
providerのソースコードを読んでもデフォルト値が分からないのですが、おそらく https://app.datadoghq.com
の値がセットされてしまってるのかなと推定しています。
今回東京リージョン(AP1)を使っています。東京リージョンのURLは https://ap1.datadoghq.com
なのでこの値api_urlを明示的にセットしてあげることで、planが通るようになりました。
variable "datadog_api_url" { type = string default = "https://ap1.datadoghq.com" } provider "datadog" { api_key = var.datadog_api_key app_key = var.datadog_app_key api_url = var.datadog_api_url }
割り当てと上限 | Cloud SQL ドキュメント | Google Cloud にありますが、max_connectionsフラグを設定することで設定変更できます。
MySQL max_connections フラグを使用すると、接続数の上限を構成できます。MySQL で最大 100,000 件の接続が可能です。データベースに接続して次のコマンドを実行すると、インスタンスの接続数上限を確認できます。 SHOW VARIABLES LIKE "max_connections";
PostgreSQL max_connections フラグを使用すると、接続数の上限を構成できます。Cloud SQL for PostgreSQL インスタンスを作成すると、マシンタイプ構成設定により、選択したコア数に基づき、自動的に利用可能なメモリサイズの範囲が調整されます。これにより、インスタンスに設定される当初のデフォルトの接続数上限も設定されます。
データベースに接続して次のコマンドを実行すると、インスタンスの接続数上限を確認できます。SELECT * FROM pg_settings WHERE name = 'max_connections';
terraform で書くとするとこんな感じでしょうか。
resource "google_sql_database_instance" "main" { name = "db_instance" database_version = "POSTGRES_14" region = "asia-northeast1" settings { tier = "db-f1-micro" database_flags { name = "max_connections" value = 100 } } }
なお、デフォルトのmax_connectionsはメモリーの容量によって自動的に設定されます。db-f1-microならメモリ0.6GBなのでmax_connectionsは25となります。
DB接続回りでほかに注意する点としてはCloud Runインスタンス当たりの接続数は100までに制限されています。
Cloud Run に関する上限 Cloud Run サービスでは、Cloud SQL データベースに対する接続数が 100 に制限されています。この上限はサービス インスタンスごとに適用されます。つまり、Cloud Run サービスの各インスタンスはデータベースに対して 100 接続を保持できるため、スケールした場合にデプロイあたりの接続の合計数が増加する可能性があります。
忘れがちな脳への覚え書きです
正確にはここを読んでください。
Amazon EventBridge input transformation - Amazon EventBridge
サンプルとして、S3バケットのCreate Objectを拾って、バケット名とオブジェクトをECSタスクに環境変数として引き渡す例を記載します。
locals { name = "eventbridge-input-test" ecs_task_definition_arn = "タスク定義ARN" ecs_cluster_arn = "ECS Cluster ARN" security_group_id = "ECSタスクのセキュリティグループ" subnets = ["ECSタスクのサブネット"] } module "source_bucket" { source = "terraform-aws-modules/s3-bucket/aws" version = "3.14.0" bucket = "${local.name}-source" versioning = { enabled = true } } # XXX: s3モジュール単体では通知が対応していないので、ここで別途設定する resource "aws_s3_bucket_notification" "source_bucket" { bucket = module.source_bucket.s3_bucket_id eventbridge = true } module "collect_event_csv" { source = "terraform-aws-modules/eventbridge/aws" version = "1.17.3" create_bus = false role_name = "${local.name}-event" attach_ecs_policy = true ecs_target_arns = [local.ecs_task_definition_arn] rules = { "${local.name}-target-ecs" = { description = "create object event" event_pattern = jsonencode({ "source" : ["aws.s3"], "detail-type" : ["Object Created"] "detail" : { "bucket" : { "name" : ["${module.source_bucket.s3_bucket_id}"] } } }) } } targets = { "${local.name}-target-ecs" = [ { name = "${local.name}-target-ecs" arn = local.ecs_cluster_arn attach_role_arn = true ecs_target = { launch_type = "FARGATE" task_count = 1 task_definition_arn = local.ecs_task_definition_arn network_configuration = { assign_public_ip = true subnets = local.subnets aws_security_groups = [local.security_group_id] } } input_transformer = { input_paths = { # $のあとにeventに存在する目的のパスを指定する。この場合S3のオブジェクト作成Eventからバケット名が取得出来る source_bucket = "$.detail.bucket.name" target_object = "$.detail.object.key" } input_template = <<TEMPLATE { "containerOverrides": [ { "name": "${local.container_name}, # 書き換えるコンテナ名を指定 "environment": [ # input_transformer.input_pathsで指定したsource_bucketの値が実行時に変換される { "name": "SOURCE_BUCKET", "value": <source_bucket> }, { "name": "TARGET_OBJECT", "value": <target_object> } ] } ] } TEMPLATE } } ] } }