東京大学のAWS入門資料がいい感じだったので最新のAWS環境でも実行できるようにしてみた

# きっかけ
東京大学のAWS講義「[コードで学ぶAWS入門](https://tomomano.github.io/learn-aws-by-coding/)」、いわゆる東大AWSってやつが良いらしいと聞いたのでやってみました。
確かにこれは良いです。クラウドをこれから学びたい方にぜひおすすめ。

https://tomomano.github.io/learn-aws-by-coding/

集中講義的に休日に半日もあれば学べます。

かかるAWS費用もわずか。
ほとんどのチュートリアルがAWSの無料枠で実行できてしまいます。ディープラーニング用のGPUインスタンスをぶん回すところは有料です。それでも数百円で済みます。
これは一通りハンズオンをやってみたAWSの費用です。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3113132/f2d8ce4d-a8e0-a2db-94fe-cd36417b2242.png">

**もはや学ばない理由が見当たりませんね。**

## これを書いた理由
けっこう有名な講義資料なのでいまごろ紹介するまでもないネタかと思っていましたが、いざやってみたら**ハンズオンのコードが最近のAWS環境では動作しない箇所がいくつかあった**ので。
**動作するように修正した手順をまとめておきました。**

<br />

本記事が[はてブを950件ももらってしまいました。](https://b.hatena.ne.jp/entry/s/qiita.com/ampspeed/items/94cae966ba2cdc4fac10)Qiitaでもストックに保存できるので、あとでゆっくりでもいいのでハンズオンに挑戦していただければ嬉しいですね。Qiita的にはストックやいいねで保存してもらったほうがよろしいでしょうし。


## 改めておすすめです（くどい）
- CDKで操作するので実践的
- 時間に制約がある大学講義がベースなので、短時間で要点を学習できる質と量
- 説明が丁寧で自習できる（重要！）
- わからなくてもとりあえずAWSを体験してみるという使い方でも良い
- AWSコンソールは英語ベースで説明しており実践的（AWSの最新機能に触れるためにも英語に慣れていた方がいいよ）

要するに、大学の基礎教養の講義資料なのですが

:::note info
学生さんに限らず仕事でAWSを使おうかなという人にもおすすめ。
:::

注）講義資料の商用利用はできないので、社内教育には使えないかなー。いや、これぐらいは家で自習しなさい。

## 講義資料の問題点と改善策
AWSは日々進化するので、講義資料の内容がところどころ古くなってしまっています。概念や構造の説明は今でも通用するので大丈夫ですが、個々のハンズオンで使用するスクリプトのバージョン互換性に問題が生じています。
すばらしいハンズオン教材なのですが、日々進化するIT業界の宿命。
実際に最新のAWS環境では講義資料のスクリプトがエラーで止まってしまう箇所があります。
そこで適宜修正しながら進めた手順をまとめました。

# ハンズオンをやってみる
## 下準備
### 端末の用意
[Windows端末でも練習できる](https://tomomano.github.io/learn-aws-by-coding/#sec:install_wsl)のですが、普段使いの端末では環境依存などがあると面倒なので、今回は仮想環境を使い練習専用のUbuntu端末を新たに用意しました。
```
$ lsb_release -d
Description:	Ubuntu 22.04.4 LTS
```
結果としてまっさらな端末からスタートするとレッスンと同じ環境が構築できるのであとが楽です。

### AWSのシークレットキーを作成
[レッスンの手順通り](https://tomomano.github.io/learn-aws-by-coding/#aws_secrets)にキーを作成して練習用端末の`~/.aws/`へ仕込みます。

### Docker実行環境を構築
ハンズオン環境を構築する手間を減らすため、Dockerを使います。
[レッスンを参考に](https://tomomano.github.io/learn-aws-by-coding/#sec:install_docker)Dockerコマンドを実行できる環境を整えます。

### 開発環境のコンテナにパッチを当てる
[ハンズオンで提供されているコンテナ](https://tomomano.github.io/learn-aws-by-coding/#sec_handson_docker)を使うと必要なものがすぐに揃うので良いのですが、内部のソフトウェアのバージョンがやや古いのでバージョンアップしながら環境を構築します。

具体的なアップデート箇所は、ハンズオン用のコンテナで提供されている開発環境はCDK v1ベースなのですが、v1はAWSのサポートが切れておりそのうち使用できなくなる懸念があるのでハンズオンのコンテナをCDK v2ベースに改造したものを作ってから始めます。

以下の内容の`Dockerfile`を用意します。
> Docker初心者向けに補足。ファイル名"Dockerfile"のテキストファイルを作ります
```
FROM tomomano/labc:latest

# Update tutorial files for CDK v2
RUN cd /root
RUN git clone -b feature/to_cdkv2  https://github.com/takashi-uchida/learn-aws-by-coding.git v2
RUN rm -rf handson ; mv v2/handson . ; rm -rf v2

# Update environment
RUN npm install -g n
RUN n stable
RUN npm i -g aws-cdk@latest
```
この内容は、元々のハンズオン用のコンテナを[こちら](https://github.com/takashi-uchida/learn-aws-by-coding/tree/feature/to_cdkv2/handson)にあるCDK v2対応のものを使うように改変するものです。CDKコマンド群もv2へアップグレードします。

先ほど作成したDockerfileを格納しているディレクトリでコンテナイメージを作るコマンドを叩きます。
```
$ docker build -t labc_v2 .
```
イメージが生成されたことを確認
```
$ docker images
REPOSITORY      TAG       IMAGE ID       CREATED         SIZE
labc_v2         latest    fe0d4423e40c   3 days ago      2.37GB
```
コンテナを起動します。
```
$ docker run -it --rm -v ~/.aws:/root/.aws:ro labc_v2

（コンテナのシェルが立ち上がります）
root@aws-handson:~$
root@aws-handson:~$ cdk --version
2.147.2 (build f4b0897)
```
先ほどの起動コマンドは、[15.8. ハンズオン実行用の Docker image の使い方](https://tomomano.github.io/learn-aws-by-coding/#sec_handson_docker)に書いている
```
$ docker run -it -v ~/.aws:/root/.aws:ro tomomano/labc:latest
```
を置き換えたものです。

以降は先ほど作ったDockerコンテナの中に入ってハンズオンのレッスンを進めます。それでは[ハンズオンの4章](https://tomomano.github.io/learn-aws-by-coding/#sec_first_ec2)から手を動かしていきましょう！

## ハンズオン実行時にハマったところと対策
ハンズオン用のファイルは2年ほど前のものであるため、昨今のAWSの仕様変更に沿わないことで実行時にエラーが発生してハマる箇所があります。そこで対応メモを書いておきます。
以降、特に注意書きしているところ以外は基本的に先ほど作成したハンズオン用コンテナ内で実行したコマンドです。

### 初回実行時
CloudFormationに古いCDKが入っていると、`$ cdk deploy`で失敗します。
AWSコンソールにて、`CloudFormation > Stackes`でCDKToolkitを一度削除してください。その後
```
$ cdk bootstrap
```
で再構築します。`CloudFormation > Stackes`の中身が空っぽな人は初期設定が完了していないので、同じく上記コマンドを実行してください。

### 6. Hands-on #2: AWS でディープラーニングを実践
EC2の上限リミットを上げてもらう手続きが必要です。デフォルトでGPUインスタンスは使用できないので上限を上げてもらいます。
最近AWSへの申請手順が変わりまして、講義資料に記載されている手順は古いです。次のような手順で申請します。

1. AWSコンソールで `Service Quotas > AWS services > Amazon Elastic Compute Cloud (Amazon EC2) ` を開く
2. 表示言語を英語にする (現在この申請手順は英語のみ対応のため）
3. 検索窓に `Running On-Demand G` と入力
4. 項目 `Running On-Demand G and VT instances` をクリック 
5. `Request increace at account level` ボタンをポチる
![request.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3113132/38a1b6e2-f5d3-9a47-4d4c-31770d92cfd2.png)
6. EC2のGインスタンス（GPUタイプ）の上限アップ申請画面が出るので必要なvCPU数を入力して送信
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3113132/85f7af1d-b4c4-e84b-3154-7378aa253aa4.png">

:::note warn
申請する数値はvCPU数です
:::

ハンズオンで使用するEC2インスタンスg4dn.xlargeは1インスタンスでvCPUを4個使うので'4'で申請します。
Hands-on #4のバッチ処理でg4dn.xlargeを複数個同時に立ち上げたい場合はvCPUを4個づつ増やして申請します。

7\. 申請が送信されると `Service Quotas > Quota request history` 上にサポートチケットのリンクが生成され、サービスセンターの画面でAWSサポートとやりとりが始まる
8. 3時間ぐらい後に「依頼を却下する。どうしても必要ならば使用目的を教えて。」と返事が来た
9. しょうがないので「I plan to use one g4dn.xlarge host for experimental purpose for a few weeks.」（実験目的でチョットダケツカイタイ）と返信。
10. 3日後、「許可したよ！ はじめの塩対応はボットによる自動応答だったんだごめんね」とサポートから打診 

こんなやりとりでGインスタンスのvCPU増加許可をもらえました。他の記事を見ると申請が何度も却下されたケースもあるようなので、どれぐらいの物量をどれぐらいの期間使いたい、と定量的に依頼を明記した方が良いのではないかと思います。相手はマシンリソースの配分を気にしているのですから。


### 6.7. 実践ディープラーニング! MNIST手書き数字認識タスク
[MNISTデータセットの本家サーバ](http://yann.lecun.com/exdb/mnist/)が落ちていて、ハンズオン実行時に行われるデータセットのダウンロードスクリプトが失敗に終わるケースがとても多いです。そこでハンズオンを始める前にダウンロード先URLを稼働率の高いミラーサーバへ変更しておきます。

Jupyter Notebookで作業を始めるところまでレッスンを進めたところで、Jupyter NotebookのWeb画面上で`/anaconda3/envs/pytorch_p36/lib/python3.6/site-packages/torchvision/datasets/mnist.py`を開いてデータダウンロード先URL定義部分を修正します。

```
(書き換え前)
    resources = [
        ("http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz", "f68b3c2dcbeaaa9fbdd348bbdeb94873"),
        ("http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz", "d53e105ee54ea40749a09fcbcd1e9432"),
        ("http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz", "9fb629c4189551a2d022fa330f9573f3"),
        ("http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz", "ec29112dd5afa0611ce80d1b7f02629c")
    ]
	
(書き換え後)
	    resources = [
        ("https://storage.googleapis.com/cvdf-datasets/mnist/train-images-idx3-ubyte.gz", "f68b3c2dcbeaaa9fbdd348bbdeb94873"),
        ("https://storage.googleapis.com/cvdf-datasets/mnist/train-labels-idx1-ubyte.gz", "d53e105ee54ea40749a09fcbcd1e9432"),
        ("https://storage.googleapis.com/cvdf-datasets/mnist/t10k-images-idx3-ubyte.gz", "9fb629c4189551a2d022fa330f9573f3"),
        ("https://storage.googleapis.com/cvdf-datasets/mnist/t10k-labels-idx1-ubyte.gz", "ec29112dd5afa0611ce80d1b7f02629c")
    ]
```

### 9. Hands-on #4: AWS Batch
これも先ほどと同様にMNISTのデータセットダウンロード先URLを修正する対応をします。
```
~/handson/aws-batch/docker/Dockerfile を編集

（追記）
# Patch to modify download URL
RUN sed -i 's/http:\/\/yann.lecun.com\/exdb/https:\/\/storage.googleapis.com\/cvdf-datasets/g' /usr/local/lib/python3.8/dist-packages/torchvision/datasets/mnist.py

# Download MNIST dataset and save in /${HOME}
```
sedを使い、docker image生成時にライブラリを修正するパッチを当てる仕掛けです。`# Download MNIST...`行の直前に追記します。

さらに実行環境のバージョン指定が古いことが原因で、deploy時にS3バケットを用意するところでエラーが発生して止まってしまうので、`~/handson/aws-batch/requirements.txt`を修正します。
```
requirements.txt を修正

（修正前）
aws-cdk-lib==2.72.0

（修正後）
aws-cdk-lib==2.80.0
```

### 9.7. Docker image を ECR に配置する
ハンズオン環境のコンテナにはDockerコマンドが入っていないので、コンテナイメージを作成してECRへアップロードする手順はハンズオン環境のコンテナをホストしている端末側で行います。
`~/handson/aws-batch/docker`ディレクトリごとSCPを使用してホスト端末へコピーしてから実行します。前章で修正したDockerfileを使うことを忘れずに。

### 9.8. 単一のジョブを実行する
ハンズオン環境へはrootユーザーでシェルに入っているのですが、Jupyter notebookサーバはセキュリティ対策のためroot権限による実行をデフォルトでは許していないので（ポート8888なので一般ユーザー権限で十分）チュートリアル通りに起動できません。``--allow-root``オプションをつけて起動します。
```
# .env の仮想環境にいることを確認
(.env) $ cd notebook
(.env) $ jupyter notebook --allow-root 
```
Jupyter notebookはハンズオンのコンテナ内で稼働しているので、WebブラウザからアクセスするためにSSHのポートフォワードを使いました。
（dockerの-pオプションは思ったように動作しなかったのでSSHを使用）
```
ハンズオン環境のコンテナを稼働させているホスト端末側にて

$ docker ps
CONTAINER ID   IMAGE    ....
8816a77aadbc   labc_v2  ....

コンテナID指定でハンズオン環境のシェルに入る
$ docker exec -ti 8816a77aadbc bash

ハンズオン環境からホスト端末へSSHでポートフォワード 
root@aws-handson:~$ ssh -R localhost:8931:localhost:8888 (ホスト端末のログインID)@(ホスト端末のIP)
```
この状態のまま、ホスト端末上でWebブラウザを立ち上げて`http://localhost:8931`でJupyter notebookへ入れます。

### 12.1. Lambda ハンズオン
Python 3.7の[ランタイムはAWSがサポート終了](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html)したのでこのままではでdeploy時にエラーで止まります。
v3.9で動かすように変更してから実行します。
```
app.py を修正

(修正前)
runtime=_lambda.Runtime.PYTHON_3_7

(修正後)
runtime=_lambda.Runtime.PYTHON_3_9
```

### 12.3. S3 ハンズオン
そのまま実行するとdeployで互換性エラーが出るので、cdk libバージョンを上げる必要があります。
```
requirements.txt を修正

(修正前)
aws-cdk-lib==2.70.0

(修正後)
aws-cdk-lib==2.80.0
```

### 13. Hands-on #6: Bashoutter
そのまま実行するとpip install, deploy時にエラーが発生して止まるので、一部修正します。
`requirements.txt` と `app.py` を修正します。
```
requirements.txt を修正

(修正前)
aws-cdk-lib==2.70.0

(修正後)
aws-cdk-lib==2.80.0    <-- 修正
urllib3<1.27,>=1.25.4  <-- 追記
```
`urllib3<1.27,>=1.25.4`はpip install時に発生するライブラリの互換性エラー回避のためです。
```
app.py を修正

(修正前)
website_index_document="index.html",
public_read_access=True,
 
(修正後)
website_index_document="index.html",
block_public_access=s3.BlockPublicAccess.BLOCK_ACLS,  <-- 追記
public_read_access=True,


(修正前)
common_params = {
    "runtime": _lambda.Runtime.PYTHON_3_7,

(修正後)
common_params = {
    "runtime": _lambda.Runtime.PYTHON_3_9,  <-- 修正
```
`block_public_access=s3.BlockPublicAccess.BLOCK_ACLS,`を追記する理由は、[パプリック公開型のS3を新規生成する際のセキュリティ仕様変更](https://aws.amazon.com/jp/about-aws/whats-new/2022/12/amazon-s3-automatically-enable-block-public-access-disable-access-control-lists-buckets-april-2023/)のためです。
最終行の修正 `PYTHON_3_9` は、前述した「12.1. Lambda ハンズオン」と同様にPythonのランタイムバージョンを変更する修正です。

## 注記
ひとまずエラーを出さずに通しで動作できる最小限の修正のみやってますので、作業中はそれなりにwarningが出ます。

# おわりに
**気に入っていただけたら「いいね！」をポチっていただくと嬉しいです**🤗