AWSコンテナ設計・構築[本格]入門 を 2025年に再入門する

# はじめに
　2021年に発行された「AWSコンテナ設計・構築[本格]入門」をみなさん手に取られたことはありますでしょうか。
 概要や考え方から始まり、ハンズオンで基本的な構築、応用編まで1冊にまとまっていて、コンテナ入門の良書だと思います。私はよく忘れるので、何度も読み返してきた本になります。

https://www.sbcr.jp/product/4815607654/
 
　ただ、発行されて4年近くが経過していますので、その間にAWSの環境等も変わり、本書に記載の通りに実施できない部分もいくつか出てきていました。入門書としてハンズオンを実施しようとした際にうまくできなくて躓いてしまう方もいらっしゃるのではないかと思います。
　かくいう私も、先日、自宅のIntel PCが旅立ってしまい、AWSアカウントも心機一転準備して、再度同様の環境を準備しようとしたところ、色々と躓きました。発行後にGithubに追加された情報、本書のサポートページに掲載された情報もありますが、私自身の備忘のため、残しておきます。

#### GitHub

 https://github.com/uma-arai


### (参考)私の実行環境
参考までに今回実施した時の私の場合は以下の環境です。
　 m1 mac
　 nvm v22.14.0
 　yarn 1.22.22

 ※私はus-east-1で実施したのですが、本書に記載のハンズオンはap-northeast-1になっています。
 一通り、書き換えて記載したつもりですが、残っていたら読み替えていただければと思います。

# 躓いたところなど
以下に記載していきます。
順不同なところがあるかもしれませんがご了承ください。

## Cloud9が使えない
本書のハンズオンはCloud9上でのGit操作等がメインで進みます。
ただ、新たに作ったAWSアカウントではCloud9にはもう逢えません。Cloud9、あなたのことはずっと忘れないよ。

ということで、私はローカル環境のIDE（VScode）で実施しました。
ローカルで実行しますので、4章に記載のCloud9用IAMロール、Cloud9のストレージのリサイズはスキップしました。

nvmやyarnのインストールがありますが、ローカルでインストール済みのバージョンで進めることにしました。
（nvmの古いバージョンですと、arm系のmacには対応していないのではないかと思い、現状のバージョンで実施しました）

## CPUのアーキテクチャを意識
　arm系macでビルドする場合、CPUのアーキテクチャを意識する必要がありました。
そのままビルドすると、arm64アーキテクチャでビルドされてしまうため、docker runをした際にエラーになることがあります。

例えば以下のような形で、ビルド時にプラットフォームを指定しました。

バックエンドコンテナのビルド(P241)
```
docker image build -t sbcntr-backend . --platform linux/amd64
```

フロントエンドコンテナのビルド(P245)
```
docker image build -t sbcntr-frontend . --platform linux/amd64
```

ビルド時の実行時間が長くなるのは我慢が必要です。
私の環境ではフロントエンドコンテナのビルドは約8分/回かかりました。


## 踏み台コンテナを準備
Cloud9が使えないため、5章で紹介されている踏み台用コンテナを使ってDBへアクセスして、構築しました。
実施したのは以下の流れです。

#### 1. ECRでリポジトリ `sbcntr-base` を作成　（P217を参考）
・alpineイメージを取得、ECRへ格納
```sh
docker image pull golang:1.16.8-alpine3.13

docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}\n"

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)

aws ecr --region ap-northeast-1 get-login-password | docker login --username AWS --password-stdin https://${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-base

docker image tag golang:1.16.8-alpine3.13 ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-base:base:golang1.16.8-alpine3.13

docker  image push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-base:golang1.16.8-alpine3.13
```

#### 2. Fargate Bastionを作成（P408からP422を実施）
本書に記載の流れで踏み台コンテナを作成

#### 3. Session Manager Pluginをインストール（macOS）
インストール済みでしたら、読み飛ばしてください。
Session Manager Pluginをダウンロード

```sh
# インストールスクリプトをダウンロード
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac/sessionmanager-bundle.zip" -o "sessionmanager-bundle.zip"

# ンストールスクリプトを解凍
unzip sessionmanager-bundle.zip

# インストールスクリプトを実行
sudo ./sessionmanager-bundle/install -i /usr/local/sessionmanagerplugin -b /usr/local/bin/session-manager-plugin

# インストールの確認
session-manager-plugin --version
# バージョン情報が表示されれば、インストールは成功です。
```

#### 4. ssmで接続
以下のコマンドでSSMを使用してBastionコンテナに接続し、ポートフォワードでAurora MySQLへ接続できました。
`mi-XXXXXXXXXXXXXXXXX`は、Sesion Managerで確認できるコンテナインスタンスのインスタンスIDです。
`sbcntr-db.cluster-XXXXXXXXXXXX.ap-northeast-1.rds.amazonaws.com`はMySQLのライターインスタンスのエンドポイントです。

```sh
aws ssm start-session --target mi-XXXXXXXXXXXXXXXXX --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"portNumber":["3306"],"localPortNumber":["3306"],"host":["sbcntr-db.cluster-XXXXXXXXXXXX.ap-northeast-1.rds.amazonaws.com"]}'
```

この状態で別ターミナルを起動し、MySQLにセッションが接続できることを確認します。
```sh
 mysql -h 127.0.0.1 --port 3306 -u admin -p 
```

※MySQL Clientがインストールされていない場合は、別途インストールをしてください。
（参考）
```sh
# MySQLクライアントのみをインストール
brew install mysql-client

# パスを通す
echo 'export PATH="/usr/local/opt/mysql-client/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

# バージョン確認
mysql --version
mysql  Ver 9.3.0 for macos14.7 on x86_64 (Homebrew)
```

MySQL接続後はP309からのユーザー作成を実施します。

## Blitz.jsのmigrateコマンドがうまく動かない（P311）
バージョンや依存関係の問題でうまく動かせませんでした。少々試してみたのですが私のスキルではスッキリ解決できず、Amazon Q Developer for CLIに相談したら、純粋なJavaScriptファイルを作成し、それを直接Node.jsで実行することを提案してくれたので、そちらで回避しました。

#### ./sbcntr-frontend/にて以下のコマンドを実施してrun-seeds.jsを作成
```js
# run-seeds.jsファイルを作成
cat > run-seeds.js << EOF
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()

async function addItems() {
  // Flower
  await prisma.item.create({
    data: {
      name: "Flower",
      title: "Flower",
      favorite: true,
      img: "/flower-park.jpeg",
      updatedAt: new Date(),
    },
  });
  
  // Apple
  await prisma.item.create({
    data: {
      name: "Apple",
      title: "My apple",
      favorite: false,
      img: "/apple.jpeg",
      updatedAt: new Date(),
    },
  });
  
  // Goods
  await prisma.item.create({
    data: {
      name: "Goods",
      title: "My goods",
      favorite: false,
      img: "/goods.jpeg",
      updatedAt: new Date(),
    },
  });
  
  // Dice
  await prisma.item.create({
    data: {
      name: "Dice",
      title: "Cool dices",
      favorite: true,
      img: "/dice.jpeg",
      updatedAt: new Date(),
    },
  });
  
  // Darts
  await prisma.item.create({
    data: {
      name: "Darts",
      title: "Antique darts board",
      favorite: false,
      img: "/darts.jpeg",
      updatedAt: new Date(),
    },
  });
}

async function addNotifications() {
  for (let i = 0; i < 2; i++) {
    await prisma.notification.create({
      data: {
        title: \`通知\${i + 1}\`,
        description: "コンテナアプリケーションの作成の時間です。",
        category: "information",
        updatedAt: new Date(),
      },
    });
  }
}

async function main() {
  try {
    // アイテムを追加（既存のアイテムを削除）
    await prisma.item.deleteMany({})
    await addItems()
    console.log("Added items")

    // 通知を追加（既存の通知を削除）
    await prisma.notification.deleteMany({})
    await addNotifications()
    console.log("Added notifications")

    console.log("Seed data inserted successfully!")
  } catch (error) {
    console.error("Error seeding database:", error)
    throw error
  }
}

main()
  .catch(e => {
    console.error(e)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.\$disconnect()
  })
EOF
```

#### package.jsonの書き換え
続いて、`./sbcntr-frontend/package.json`の13行目
`"seed": "npx blitz db seed"`
を以下に書き換えました。
`"seed": "node run-seeds.js"`

#### (参考)シンプルにSQLクエリ実行
seedの中身をみましたが、私のレベルだと、シンプルにMySQLで接続してクエリを実行する方法が一番早いと思いました。。
クエリにするなら以下の流れになると思います。

```SQL
# MySQLに接続
mysql -h 127.0.0.1 -P 3306 -u sbcntruser -p

# Itemテーブルにデータを挿入
INSERT INTO Item (name, title, favorite, img, createdAt, updatedAt) VALUES 
('Flower', 'Flower', 1, '/flower-park.jpeg', NOW(), NOW()),
('Apple', 'My apple', 0, '/apple.jpeg', NOW(), NOW()),
('Goods', 'My goods', 0, '/goods.jpeg', NOW(), NOW()),
('Dice', 'Cool dices', 1, '/dice.jpeg', NOW(), NOW()),
('Darts', 'Antique darts board', 0, '/darts.jpeg', NOW(), NOW());

# Notificationテーブルにデータを挿入
INSERT INTO Notification (title, description, category, unread, createdAt, updatedAt) VALUES 
('通知1', 'コンテナアプリケーションの作成の時間です。', 'information', 1, NOW(), NOW()),
('通知2', 'コンテナアプリケーションの作成の時間です。', 'information', 1, NOW(), NOW());

# 接続を終了
exit;
```

## CodeCommitが使えない(P338)
本書ではリポジトリにCodeCommitを使用していますが、CodeCommitが新規アカウントでは使えないということもあり、今回はGithubと連携して使いました。

#### GitHub連携
CodePipelineサービスに移動
左側のナビゲーションペインから「設定」→「接続」を選択
![ecs001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/d77d4e50-6db4-470c-937d-9f719e17564b.png)

`接続を作成`を選択して、`接続を作成する`画面に遷移します。
プロバイダーは`GitGitHub`を選択し、接続名は`github-connection`を入力して、`GitHubに接続する`をクリックします。
![ecs002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/6a6f3b7b-b59e-49fc-b9c9-ffb06d6aca7a.png)

GitHubのサインイン画面が出てきますので、サインインします。

![ecs003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/1fc69f04-7d24-40a3-953c-c2b65df36f4e.png)

マネコンに戻ってきますので、`新しいアプリをインストールする`を選択して、改めてGitHubページに遷移します。
AWS Connector for GitHubのアクセスを許可すると、マネコンに戻ります。
![ecs005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/17e17f92-55d8-4e8e-88a1-7d9bd8983534.png)

アプリインストールの項目に値が入っているので、接続をクリックします。
![ecs006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/1ef66c79-bba5-4789-ab27-0ed2b0734d04.png)

コネクションが作成できました。これでCodePipelineのパイプラインから指定できるようになります。
![ecs007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/6745cea3-8f61-4d89-b0a6-ab0e0eaf6034.png)

あとはGitHubでリポジトリを作成していけば進められます。

## リポジトリ接続（ P340）
codecommitの代わりにGitHubのリポジトリURLを指定します。

```
git remote set-url origin https://github.com/xxxxxxx/sbcntr-backend.git
```
リポジトリのURLはこちら
![ecs008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/8123bf6c-9844-485a-9ada-59010996ad1a.png)

## buildspec.ymlの作成(P341)
buildspec.ymlで10-12行目を、コメントアウトしました。
現在、ランタイムとしてDockerは指定できないようです。

```yml
version: 0.2

env:
    variables:
        AWS_REGION_NAME: ap-northeast-1
        ECR_REPOSITORY_NAME: sbcntr-backend
        DOCKER_BUILDKIT: "1"
    
phases:
    #install:　#ここをコメントアウト
    #    runtime-versions:  
    #        docker: 19

    pre_build:
        commands:
            - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
            - aws ecr --region ap-northeast-1 get-login-password | docker login --username AWS --password-stdin https://${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sbcntr-backend
            - REPOSITORY_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION_NAME}.amazonaws.com/${ECR_REPOSITORY_NAME}
            # タグ名にGitのコミットハッシュを利用
            - IMAGE_TAG=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | cut -c 1-7)
    build:
        commands:
            - docker image build -t ${REPOSITORY_URI}:${IMAGE_TAG} .
    post_build:
        commands:
            - docker image push ${REPOSITORY_URI}:${IMAGE_TAG}
            - printf '{"name":"%s","ImageURI":"%s"}' $ECR_REPOSITORY_NAME $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json

artifacts:
    files:
        - imageDetail.json
```

## codebuildのソース設定 (P344)
GitHubを選択して、先ほど作成したConnectionsやリポジトリを指定します。
![ecs009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/6bdfbbd1-8c23-41b8-b7ea-99ddf6ed5b1e.png)

## taskdef.jsonの作成（P354）

ECSのタスク定義からJSONファイルをコピーして作成しますが、以下2点にご注意ください。

#### 本文に記載のある`<IMAGE1_NAME>`に書き換える
コピペした段階だと、`"image": "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1:.amazonaws.com/sbcntr-backend:0000000",`のような名称になっています。
 
#### 最後の`"tags": []`を消す。
記載を残しているとタグは空にできないため、エラーになります。


## codepipelineのソースステージの設定（P359）
GitHub用に設定しました。
アクションプロバイダーは`GitHub(GitHubアプリ経由)`を選択
接続は先ほど作成したConnectionを選択
リポジトリ名は自分のリポジトリを指定します。

![ecs010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/58048032-dc8d-4ce3-af24-e8be5b89332e.png)


# まとめ
　いかがでしたでしょうか。CloudFormationでIaC化したコードを上げてしまうこともできますが、やはり最初は手を動かして流れを把握する方が有用と考えているため、敢えてIaCは載せずにおきます。
 環境が必ずしも一致しないため、上に記載した内容でうまくいかない部分もあると思います。クラウドサービスの移り変わりは早いので、過去に出版されている本のハンズオンを完全には再現できないことがよくあります。（私はよくハマります）その際は、お近くのつよつよエンジニアに相談するか、Amazon Q Developer for CLIのAIエージェント機能に相談しながら進めていくと良いのではないかと思います。
 
 2025年に初めて本書を手に取ってコンテナに入門しようとするどなたかの一助になれば幸いです。