ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【AWS/Terraform】ALBやNLBのポートと証明書の設定について

 ALBやNLBをTerraformで作成する際、ターゲットグループ、アタッチメント、リスナーでポートやプロトコルが複数出てきてどれがどう扱われるのかかなり混乱することがありました。

 なので今回はその辺りのポートやプロトコル、証明書についてまとめた内容になります。


 では、始めます。


1:ALBやNLBのTerraformコード例
 TerraformでALBやNLBを作成するコードを書くと、基本的には以下のようになるかと思います。このまま単体ではデプロイできないのであくまで各設定値がどうなっているかの例として挙げているので注意してください。

# LB本体
resource "aws_lb" "example_lb" {
  name               = "ecample-alb"
  internal           = true
  load_balancer_type = "application" # NLBの場合はnetworkを設定
  security_groups    = [aws_security_group.lb_sg.id]
  subnets            = [aws_subnet.subnet1.id, aws_subnet.subnet2.id]
}

# LBターゲットグループ
resource "aws_lb_target_group" "example_tg" {
  name     = "example-tg"
  port     = 80
  protocol = "HTTP" # ALBの場合はHTTP or HTTPS、NLBの場合はTCP or TLSなどを設定
  vpc_id   = aws_vpc.main.id
}

# LBターゲットグループアタッチメント
resource "aws_lb_target_group_attachment" "example_tg_attachment" {
  target_group_arn = aws_lb_target_group.example_tg.arn
  target_id        = aws_instance.example_ec2.id
  port             = 8000
}

# LBリスナー
resource "aws_lb_listener" "alb_listener" {
  load_balancer_arn = aws_lb.example_lb.arn
  protocol          = "HTTPS" # ALBの場合はHTTP or HTTPS、NLBの場合はTCP or TLSなどを設定
  port              = "443"

  # 証明書が必要な場合は以下を設定
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = "arn:aws:iam::123456789:server-certificate/test_cert"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.example_tg.arn
  }
}

 protocolの設定がaws_lb_target_groupにもaws_lb_listenerにもあり、portの設定ではaws_lb_target_groupにもaws_lb_target_group_attachmentにもaws_lb_listenerにもあるのがわかると思います。

 これらについてどう使われるかを次に説明します。
 

2:ALBやNLBのポートと証明書の扱い
 例えば以下のように通信する場合を考えます。

EC2 → ALB → Fargate

 この時、それぞれ以下のようになります。

・ポートの扱い

通信経路 説明
EC2 → ALB リスナーで設定したポートのみ受け付ける。
ALB → Fargate アタッチメントのポートでターゲットに送信。アタッチメントのポートが設定されてない場合はターゲットグループのポートでターゲットに送信。

プロトコルや証明書の扱い

通信経路 説明
EC2 → ALB リスナーで設定したプロトコルで受け付ける。リスナーで設定した証明書を使用。
ALB → Fargate ターゲットグループで設定したプロトコルでターゲットに送信。証明書はターゲット側を使用。

 つまり、リスナーでプロトコルとポートと証明書を設定した場合はそのプロトコルとポート番号とリスナーに設定された証明書でリクエストを受け付けます。そしてターゲットに送信する際にはリスナーに設定した証明書は使わずに「ターゲット側の証明書を使用」して「アタッチメントのポートで上書きされたポート」で送信されるということになります。
 アタッチメントのポートが設定されていない場合は、ターゲットグループのポートで送信されます。

 なので1に挙げた例を考えると、以下のようになります。

  • 受付はポート443のHTTPSでリスナーに設定した証明書を使用
  • 送信はアタッチメントのポート8000が使用され、HTTPで送信される(HTTPなので証明書はなし)

 また1のアタッチメントを削除してターゲットグループのprotocolをHTTPS、portを443とすると以下のようになります。

  • 受付はポート443のHTTPSでリスナーに設定した証明書を使用
  • 送信はターゲットグループのポート443を使用したHTTPSで送信され、ターゲット側の証明書を使用


 以上がAWSにおけるALBやNLBのポートと証明書の設定についてになります。

 わかっている人からすると当然な内容かもしれませんが、インフラにもAWSにも不慣れな自分としてはかなり混乱して何をどう設定すればいいのかがわからない時があったのでまとめた次第です。
 同じように混乱している人の助けになれば幸いです。


・参考資料

【Docker】pgAdmin用のDockerfileを作成してみる

 pgAdminというDBへのアクセスやちょっとした監視、SQLの実行などをGUIで行えるツールがあります(詳しくは以下の公式ページを参照してください)。

 Windowsであればインストーラを手動でインストールしてデスクトップアプリとして使用することもできますが、サーバモードとしてWebサーバのような形で使用することもできます。
 サーバモードによってDBへのSQL適応などをCUIではなくWebブラウザ上のGUIから楽に行うことができるため、AWSなどでサーバモードのpgAdminを使うことも割とあるようです。

 今回はそのpgAdminを1からインストールして設定する……のではなく、Dockerfileで使用する方法の備忘録になります(1からインストールしたりするよりもDockerfileでイメージとして起動させる方が楽なのでそうしました)。


 では、始めます。


・pgAdminのDockerfile
 ある程度まで設定されたpgAdminのイメージはDockerHubに存在しています。

 なのでこのイメージをそのまま使えばよく、あとは以下の最低限のマスタユーザの設定を行えば使えます。

# マスタユーザのメールアドレス設定(xxx@yyy.zzzの形式でないとダメな様子)
ENV PGADMIN_DEFAULT_EMAIL=hoge@hoge.com

# マスタユーザのパスワード設定
ENV PGADMIN_DEFAULT_PASSWORD=pgAdminPassword

 実際に動く形のDockerfileは以下のようになります。

・Dockerfile

# pgAdmin4公式の最新イメージを使用
FROM dpage/pgadmin4:latest

# rootユーザとして実行し諸々アップデート(任意)
USER root
RUN apk update && apk upgrade
RUN apk add musl --upgrade

# pgAdminログイン用マスタユーザ環境変数を設定(必須)
ENV PGADMIN_DEFAULT_EMAIL=hoge@hoge.com
ENV PGADMIN_DEFAULT_PASSWORD=pgAdminPassword

# pgAdminのデフォルトポート80を公開(必須)
EXPOSE 80

# コンテナ起動時にpgAdminサーバ起動コマンド実行(必須)
CMD ["/bin/sh", "-c", "python3 /pgadmin4/web/pgAdmin4.py"]

 内容としては、ログイン用マスタユーザの設定をして、pgAdminのデフォルトポートである80を公開し、サーバ起動用のPythonを実行しているだけです。
「rootユーザとして実行し諸々アップデート(任意)」の部分を入れているのは、AWSのECRにイメージをプッシュした際のスキャンで引っかかる恐れがあるため、アップデートしています。

 あとはこのDockerfileをビルドしてコンテナとして立ち上げ、作業用PCからポートフォワーディング設定をしてWebブラウザからアクセスすれば、ログイン画面が表示されるはずです。


 以上がpgAdmin用のDockerfileを作成してみた内容になります。


 これを実際に使ってAWS上にこのコンテナを立ち上げたりする記事は、また後で書くかもしれません。


・参考資料

【AWS】予め用意されているAWSポリシーについて

 AWSには予め用意されているポリシーがありますが、どういう時にロールにアタッチすればよいかわからない部分が多かったりします。

 例えば、EC2のロールに対してSSMを使ったログインをしたい場合にどのポリシーをアタッチすれば良いのか、などは割と調べないと出てこなかったりします。

 なので今回は、自分が調べたり実際に使ったりしたものをまとめてみた記事になります。全てのAWSポリシーを書くことは不可能なので、わかった段階で随時更新していく感じとなるので注意してください。


 では、始めます。


AWSポリシー説明

  • AWSフルアクセス許可(デプロイ用ユーザなどで使用)
arn:aws:iam::aws:policy/AdministratorAccess
  • AuroraやRDS ProxyなどRDS系のフルアクセス許可
arn:aws:iam::aws:policy/AmazonRDSFullAccess
  • S3のフルアクセス許可
arn:aws:iam::aws:policy/AmazonS3FullAccess
  • FSxのフルアクセス許可
arn:aws:iam::aws:policy/AmazonFSxFullAccess
  • EC2のフルアクセス許可
arn:aws:iam::aws:policy/AmazonEC2FullAccess
  • ECSのフルアクセス許可
arn:aws:iam::aws:policy/AmazonECS_FullAccess
  • ECRのフルアクセス許可
arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess
  • CloudWatchSyntheticsのフルアクセス許可
arn:aws:iam::aws:policy/CloudWatchSyntheticsFullAccess
  • Lambdaのフルアクセス許可
arn:aws:iam::aws:policy/AWSLambda_FullAccess
  • SSMログイン許可
arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
  • EC2をECSクラスタに登録するための許可(ECS on EC2などで使用)
arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
  • ECRからイメージをpullするための許可(ECSタスクのタスク実行ロールなどで使用)
arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
  • AWSの様々なバックアップ系の許可
arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup
  • lambdaがVPC内のリソースにアクセスするための許可
arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole


・参考資料

【AWS/python】RDS Proxy経由でAurora PostgreSQLへの疎通確認をpythonで行う

 今回もAWSでの疎通確認についてです。タイトルにあるようにEC2またはECSコンテナからRDS Proxy経由でAurora PostgreSQLに接続できるかの疎通確認をpythonで行った備忘録になります。

 今回は前に説明した以下の記事のElastiCache Redisの疎通確認以上に前提が複雑なので、同じコードを実行する場合はしっかり確認した上で行ってください。

 AWSリソースの作り方などは説明しませんが、より詳細な情報については参考資料に挙げているページ様を参照してください。


 では、始めます。


0:前提
 今回の前提は以下のようになります。前提条件が多いですが、頑張ってAWSリソース構築を行ってください

  • DB接続情報(dbname, port, username, password)がAWS Secretsに登録済み
  • 疎通元のEC2またはECSコンテナがAWS Secretsにアクセス可能
  • RDS ProxyのTLS接続が有効
  • RDS ProxyはIAM認証
  • Aurora PostgreSQLは通常のユーザ名/パスワード認証
  • その他AWSリソースは全て正常に作成されている


1:実行環境構築
 前の記事と同じくEC2ならAnsible、ECSならDockerfileによるイメージ作成で、まずはpythonとpipの環境を構築します。
 そしてpipを使って以下の2つをインストールします。

pip install psycopg2-binary
pip install boto3

 次にTLS接続を行うために証明書を用意します。

 適切な証明書がない場合は以下のAmazonのページにある「Root CAs」の表の一番上の行の「Self-Signed Certificate」の「PEM」をクリックしてダウンロードしたものを使用してください。

 用意した証明書をEC2またはECSコンテナにアップロードします。

 これで実行環境の構築は終了です。


2:疎通確認コード
 実行環境ができたら以下のファイルをコピペし、証明書ファイルパス、RDS Proxyエンドポイント、シークレット名、リージョン名をそれぞれ適切なものに書き換えて保存します。

・connect_db_via_proxy.py

# -*- coding: utf-8 -*-
import psycopg2
import boto3
import json


def main():
  # 以下の4つの変数を適切なものに書き換える
  sslcert_path = "./xxxxxx.pem" # SSL証明書ファイルパス
  db_endpoint = "xxxxxxxxxxxxxxx.rds.amazonaws.com" # RDS Proxyエンドポイント
  secret_name = "db-secrets" # DB情報のシークレット名
  region_name = "ap-northeast-1" # リージョン名

  # Secrets Manager から認証情報を取得
  db_credentials = get_secret(secret_name, region_name)

  # データベース接続情報
  db_port     = db_credentials["port"]     # ポート番号
  db_name     = db_credentials["dbname"]   # データベース名
  username    = db_credentials["username"] # ユーザー名

  # 取得したDB情報を表示
  print(db_name)
  print(username)
  print(db_port)
  print("Get DB Secrets.")

  # IAM認証用にトークンを作成
  token = get_iam_token(db_endpoint, db_port, username, region_name)
  print(token)
  print("Create token.")

  # データベースに接続
  connection = psycopg2.connect(
    sslmode="require",        # RDS ProxyのTLS接続を有効にする
    sslrootcert=sslcert_path, # 証明書設定
    connect_timeout=10,
    host=db_endpoint,
    port=db_port,
    dbname=db_name,
    user=username,
    password=token
  )

  print("Successfully connected to the database!")

  # カーソルを作成してクエリを実行(ここではDB名を取得)
  cursor = connection.cursor()
  cursor.execute("SELECT current_database();")
  result = cursor.fetchone()
  print(f"Current database: {result[0]}")

  # 接続を閉じる
  cursor.close()
  connection.close()


# Secrets Manager から認証情報を取得する関数
def get_secret(secret_name, region_name):
  # セッションとクライアント作成
  session = boto3.session.Session()
  client = session.client(
    service_name='secretsmanager',
    region_name=region_name
  )

  try:
    # シークレット取得
    response = client.get_secret_value(SecretId=secret_name)
    # Secrets の値を JSON 形式でデコード
    secret = json.loads(response['SecretString'])
    return secret
  except Exception as e:
    print(f"Failed to retrieve secret: {e}")
    raise


# IAMトークンを生成する関数
def get_iam_token(proxy_endpoint, port, username, region_name):
  rds_client = boto3.client("rds", region_name=region_name)
  token = rds_client.generate_db_auth_token(
    DBHostname=proxy_endpoint,
    Port=port,
    DBUsername=username,
    Region=region_name
  )
  return token


if __name__ == '__main__':
  main()

 上記のコードを実行すると色々とprintで表示されますが、最終的に以下の表示が出ていればRDS Proxy経由でのDBへの疎通ができています。

Successfully connected to the database!
Current database: [データベース名]


 以上がRDS Proxy経由でAurora PostgreSQLへの疎通確認をpythonで行う方法になります。

 正直まだAWS自体もちゃんと理解はできていないですが、このやり方で疎通確認はできたので困っている人の参考になれば幸いです。


・参考資料

【AWS/python】ElastiCache Redisへの疎通確認をpythonで行う

 AWSでEC2やECSコンテナに何かアプリを実行させ、ElastiCache Redisのキャッシュを使って処理するような場合が結構あります。
 AWS上でリソースを構築した際にちゃんとできているかの疎通確認を行いたい場合がありますが、案外そのやり方がすんなり見つからなくて苦労しました。

 なので今回はそんなElastiCache Redisへの疎通確認をpythonで行う場合の備忘録となります。詳しい解説は参考資料に挙げているページ様を参照してください。

 また、AWSリソース自体を構築する説明はしません。AWS上にリソースが構築済みであとは疎通確認をするだけという前提で始めるので注意してください。


 では、始めます。


0:前提
 冒頭でも少し書きましたが、以下の状態である前提で話を進めます。

  • EC2またはECSコンテナからElastiCache Redisへの疎通確認とする
  • EC2またはECSコンテナからElastiCache RedisへのセキュリティグループなどAWSのリソース設定が正しく構築されている


1:実行環境構築
 EC2ならAnsible、ECSならDockerfileによるイメージ作成となるかと思いますが、まずはpythonとpipの環境を構築します。
 そしてElastiCache Redisへアクセスするためのライブラリであるredisの最新版をpipでインストールします。

pip install --upgrade redis

 これで実行環境の構築は終了です。


2:疎通確認のコード(クラスターなし)
 ElastiCache Redisでクラスターなし構成の場合は以下のpythonコードをコピペし、Redisのエンドポイントの部分を書き換えます。

・redis_non_cluster.py

# -*- coding: utf-8 -*-
import redis

# Redisの接続情報
redis_host = "clustercfg.xxxxxxxxxxxxxxxxxxx.cache.amazonaws.com" # Redisのエンドポイント
redis_port = 6379
timeout    = 30  # タイムアウト時間(秒)

# Redisクライアントを初期化
redis_client = redis.StrictRedis(
  host=redis_host,
  port=redis_port,
  decode_responses=True,
  ssl=True,               # Redis Cache側でat_rest_encryption_enabledやtransit_encryption_enabledがTrueならSSL接続になるのでTrueにする必要がある
  socket_timeout=timeout  # タイムアウト時間を設定
)

try:
  # PING コマンドで接続確認
  if redis_client.ping():
    print("Redisへの接続が成功しました。")

    # データをキャッシュに保存
    redis_client.set("key", "hoge")
    # キャッシュからデータを取得
    value = redis_client.get("key")
    # 以下の内容が表示されれば接続できている(タイミングによっては失敗するが何度かやって表示されればOK)
    print(f"Retrieved value: {value}")
    # キャッシュから削除
    redis_client.delete("key")
  else:
    print("Redisへのpingに失敗しました。")
except redis.ConnectionError as e:
  print(f"エラーが発生しました。: {e}")

 上記のpythonコードを実行して疎通に成功すると以下のような表示になります。

Redisへの接続が成功しました。
Retrieved value: hoge


3:疎通確認のコード(クラスター有効)
 ElastiCache Redisでクラスター有効構成の場合は以下のpythonコードをコピペし、Redisのエンドポイントの部分を書き換えます。

・redis_cluster.py

# -*- coding: utf-8 -*-
import redis

# Redisクラスタの接続情報
redis_host = "clustercfg.xxxxxxxxxxxxxxxxxxx.cache.amazonaws.com" # Redisのエンドポイント
redis_port = 6379
timeout    = 30  # タイムアウト時間(秒)

# Redisライブラリのclusterモジュールを使用
redis_client = redis.RedisCluster(
  host=redis_host,
  port=redis_port,
  decode_responses=True,
  ssl=True,               # Redis Cache側でat_rest_encryption_enabledやtransit_encryption_enabledがTrueならSSL接続になるのでTrueにする必要がある
  socket_timeout=timeout  # タイムアウト時間を設定
)

try:
  # 疎通確認
  if redis_client.ping():
    print("Redisクラスタへの接続が成功しました。")
    # データをキャッシュに保存
    redis_client.set("key", "hoge")
    # キャッシュからデータを取得
    value = redis_client.get("key")
    print(f"Retrieved value: {value}")
    # キャッシュから削除
    redis_client.delete("key")
  else:
    print("Redisクラスタへのpingに失敗しました。")
except Exception as e:
  print(f"エラーが発生しました。: {e}")

 基本的には2と同じですが、クラスター用の「RedisCluster」モジュールを使用するようにしています。

 こちらも同じようにpythonコードを実行して疎通に成功すると以下のような表示になります。

Redisクラスタへの接続が成功しました。
Retrieved value: hoge


 以上がElastiCache Redisへの疎通確認をpythonで行う方法になります。

 最近AWSを触ることが出てきているのですが、案外こういう疎通の具体的な方法がぱっと見つからなかったりしたので記事にしてみた次第です。

 AWSやTerraformに関するリソース構築についてもどこか時間があれば記事にしていきたいのですが、ひとまず自分が躓いたところをちょっとずつ記事にしていこうと思います。


・参考資料

【Terraform】Terraformでモックを使ったテストの書き方(Mock Test)

 最近Terraformを触る機会があったのですが、モックを使って動作を確認したいと思って調べると、Terraformでもモックを使えるようでした。
 比較的新しい機能らしく、ネットで検索してもS3バケットのモックばかり出てきて、実用的なモックの書き方が見当たりませんでした。

 悩みながら色々やった結果、それなりに書き方がわかってきたので今回はその備忘録になります。

 自分なりにまとめたものなので「間違っていたり、他に良い書き方がある可能性がある」ので注意してください。


 では、始めます。


1:環境について
 前提として、TerraformとAWS CLIの設定を済ませておいてください。今回の実行環境での各バージョンは以下のようになります。

名称 バージョン
Terraform 1.9.5
AWS CLI 2.17.63


2:Terraformのテストファイルについて
 Terraformのルートモジュール配下に「.tftest.hcl」拡張子のファイルを作成すると、自動的にそれがTerraformのテストファイルとみなされます。
 またルートモジュール配下に「tests」ディレクトリを作成し、その中に複数の.tftest.hclファイルを作成すると「terraform test」コマンドで、まとめてテストファイルを実行できます。

 自分で動作確認した範囲では、.tftest.hclファイルの中ではlocalsやvariablesでの変数は使えないようです。
 モックにしたい部分は基本的にはdataリソースを使って検索する形にするのが良さそうだと思っています。


3:Terraformのテストの書き方
 Terraformのテストでは、以下の内容を書けていれば基本的には動きます。

・モック用プロバイダ(あった方が良い)

mock_provider "aws" {
  alias = "fake"
}

・dataリソースを上書き(モック)

override_data {
  target = data.xxx.yyy # ルートモジュール内にある対象リソース

  # モックとして返す値の設定(複数の要素を設定可能)
  values = {
    id = "xxx-id"
    ids = ["id1", "id2"]
    name = "xxx-name"
  }
}

・テスト実行部分

# runの次がテスト名
run "test_name" {
  command = plan  # planコマンドでテスト

  # モック用プロバイダを使用
  providers = {
    aws = aws.fake
  }

  # assertで結果を確認(assertを複数書くことも可能)
  assert {
    condition     = aws_xxx.id == "hogehoge" # 結果比較
    error_message = "Error!"                 # 結果が違う場合の表示メッセージ
  }
}

 それぞれについては内容を読めば理解できるかと思います。

 上記の内容をまとめてより具体的にした例が以下になります。

# --- モックを含むテストの例 ---

# モック用プロバイダ
mock_provider "aws" {
  alias = "fake"
}

# dataの内容をモックの値で上書き
override_data {
  target = data.aws_vpc.search # 対象リソース
  # モックで返す値
  values = {
    # VPCのidしか使わないのでidのみをモックにする
    id = "vpc-mock-id"
  }
}

# テスト実行
run "subnet_test" {
  command = plan  # planコマンドでテスト

  # モック用プロバイダを使用
  providers = {
    aws = aws.fake
  }

  assert {
    # 作成したsn1のサブネットのVPC IDがモック通りかを確認
    condition = aws_subnet.sn1.vpc_id == "vpc-mock-id"
    error_message = "aws_subnet sn1 create Error!"
  }
}


4:Terraformのモックテストのサンプル
 上記までの説明を見ても実際に動かしてみないとわからないと思うので、以下にモックを用いたTerraformテストのサンプルを公開しています。

 git cloneやzipダウンロードして解凍したそのディレクトリに移動し、「terraform init」コマンドをしてから「terraform test」コマンドを実行するとテストが行われます。
 conditionなどの値を変更してテストを実行するとエラーを出すこともできるので、色々と試してみてください。


 以上がTerraformでモックを使ったテストの書き方になります。

 Terraformのモックで悩んでいる方の参考になれば幸いです。


・参考資料

【Windows】Windows11で環境変数の設定画面を出す方法

 久々にWindows11で環境変数に手動でパスを通す必要が出てきたのですが、環境変数の設定画面への行き方を完全に忘れていました。

 毎回調べたりするのが面倒なので、今回は環境変数の設定画面を出す方法を記事に残しておこうと思った次第です。


 では、始めます。


・Windows11で環境変数の設定画面を出す
 まずは「設定」アプリを開きます。

 設定アプリの左側メニューにある「システム」をクリックし、出てきた中にある「バージョン情報」をクリックします。

 バージョン情報画面の「デバイスの仕様」の下にある「システムの詳細設定」をクリックします。

 システムのプロパティが開くので上部タブの「詳細設定」をクリックした中にある「環境変数」ボタンをクリックします。

 環境変数設定画面が出てくるので、環境変数の追加や編集などを行います。


 以上がWindows11で環境変数の設定画面を出す方法になります。

 環境変数へパスを通すのは開発をやっているとそれなりに出くわしますが、毎回設定画面への行き方を忘れてしまっています。
 マイクロソフトさんには、もっとわかりやすいところに項目として出したりして辿り着きやすくしてほしいですね…。


・参考資料