- 投稿日:2020-09-28T23:27:09+09:00
何も考えずにEC2にDjangoプロジェクトを展開してブラウザから管理者画面を表示するまでのメモ
はじめに
AWSのEC2上でDjangoプロジェクトを展開しようとして丸一日悩んでしまったので、手順をまとめました。あくまでも自己責任でお願いします。
やったこと
EC2インスタンスを立てて何も考えずにDjangoを動かして、ネット上からアクセスできるようにする。
注意
- ec2-userをそのまま使っていたり、セキュリティ的に怪しい所があるため注意すること。今回はAWSでdjangoプロジェクトをとりあえず動かせるようにすることを目的とする。
- VPC、サブネット、インターネットゲートウェイの設定はすでに完了していて、EC2自体はネットから参照できる状態になっている所からスタートする
- ssh(22)、HTTP(80)とDjango用(8000)のポートが開放されている状態を前提とする
- DBはSQLite3を使用する。また、SQLite3のバージョンを最新にするため既存のソフトウェアでSQLite3を使用している際は注意すること。
- Djangoの管理者画面がスタイルシートなどを含めて読み込めたところが本記事のゴールとする
参考にした文献
- 【20分でデプロイ】AWS EC2にDjango+PostgreSQL+Nginx環境を構築してササッと公開
- AWSにDjangoアプリケーションをデプロイ(Nginx, gunicorn, postgresql)
- AWS Amazon Linux2 で Django 2.2以降の環境を構築する
- 【AWS】EC2でWebサーバの構築 – Nginx構築 -【Nginx】
- Nginxで躓いたところ及びメモ
必要なものを揃える
本記事ではvenvなどは使わず、そのままpython3を使っていくため注意すること。
install$ sudo yum update $ sudo yum install python3 # Amazon Linux 2で$sudo yum install nginx と入力するとコマンドを催促されるので、それを入力する。 $ sudo amazon-linux-extras install nginx1 $ sudo pip3 install django $ sudo pip3 install gunicornAmazon Linux 2にもデフォルトでSQLite3がインストールされていたが、Djangoで求めるバージョン(3.8.3以降)にマッチしないため、SQLiteのアプデを実施する。
SQLite3のinstall# 後述のSQLite3インストール時のmakeファイルのビルドに使用する。 $ sudo yum install gcc # 最新版のSQLite3をインストールする。執筆当時は3.33.0が最新だった。(https://www.sqlite.org/download.html) # 展開した後にmakeファイルをビルドしてmake installまで実施する $ wget https://www.sqlite.org/2020/sqlite-autoconf-3330000.tar.gz $ tar zxvf sqlite-autoconf-3330000.tar.gz $ cd sqlite-autoconf-3330000 # makeファイルのビルド $ ./configure --prefix=/usr/local $ sudo make $ sudo make install次に、過去に入っていたsqlite3から現行のsqlite3を使用するように設定を通す。
SQLite3のinstallつづき# すでにインストール済みのsqlite3を確認する $ sudo find / -name sqlite3 # もともとあったやつは多分これ。バージョンが古いことを一応確認しておく。 /usr/bin/sqlite3 --version # 今回インストールしたのは多分これ。バージョンが古いことを一応確認しておく。 /usr/local/bin/sqlite3 --version # 古い方のsqlite3のフォルダ名を変え、また新しい方のsqlite3へパスを設定しておく sudo mv /usr/bin/sqlite3 /usr/bin/sqlite3_old sudo ln -s /usr/local/bin/sqlite3 /usr/bin/sqlite3 # ライブラリへのパスを通しておく sudo touch ld.so.conf/sqlite3.conf sudo echo /usr/local/lib >> sqlite3.conf # 過去のsqlite3へのリンクとディレクトリを削除 $ sudo rm /lib64/libsqlite3.so.0 $ sudo rm -rf /lib64/libsqlite3.so.[過去のバージョン] # ライブラリのパスを更新する sudo ldconfig # 新しいライブラリへのパス(/usr/local/lib)が通っているか確認する $ ldconfig -p | grep sqlite # 一応バージョンも確認する sqlite3 --version最後のバージョン確認で現行のSQlite3のものが出力されれば、次に進む。
Djangoの設定
とりあえずプロジェクトを作る。プロジェクト名は公式ドキュメントでおなじみのmysiteにする。
django-admin# /home/ec2-userなどの任意のユーザのhomeディレクトリへ移動 $ cd /~ $ django-admin startproject mysite今回はadmin画面が見えればOKなので、最低限の設定だけをする。
django-admin$ cd mysite/mysite $ vi settings.pysetting.pyにおいて設定するのは
- 末尾にSTATIC_ROOTを追加(あわせてimport osも追記)
- ALLOWED_HOSTSに自分のパブリックドメインやパブリックIPアドレスを設定
ぐらい。一応全文を載せておく
setting.pyimport os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'hdxr$tlb=r4ns*#rg806lr*=(aa=_gny4k(1tm9n0atev0n5hs' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True # 自分のドメインやパブリックIPアドレスとか ALLOWED_HOSTS = ['*.*.*.*',] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'mysite.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'mysite.wsgi.application' # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR,'static')終わったら、djangoプロジェクトの静的ファイルをまとめたり、
管理者ページに入るまでの設定をしておく。django$ cd ~/mysite # 自身の情報をここで入れておく。パスは忘れないように $ python3 manage.py createsuperuser # STATICファイルをとりまとめておく $ python3 manage.py collectstaticもし、python3 manage.py collectstaticでcheck_sqlite_versionと書かれたエラーを吐いた場合はSQLite3の設定を見直すこと。過去のSQLite3を見に行っている可能性が高い。
Djangoそのものの設定がうまくできているか確認するために、gunicornから動かしてみる。
django# 必ずmanage.pyなどが存在しているフォルダへ移動すること $ cd ~/mysite $ sudo gunicorn mysite.wsgi --bind=0.0.0.0:8000ブラウザでパブリックIPあるいはドメインへアクセスした際に、例のロケットが表示されればOK。確認できたらgunicornをCtrl+Xなどで終了する。
gunicornの設定
以下の設定ファイルを作成(編集)する。
- /etc/systemd/system/gunicorn.service
- /etc/systemd/system/gunicorn.socket
なお、gunicorn.serviceのExecStartのパスはwhichコマンドで確認する。
gunicorn.serviceのExecStartに設定するパスのチェックwhich gunicornユーザー名(グループ名)、WorkingDirectory、ExecStartのwsgiファイル名とListenStreamは各自の環境に揃えておくこと。(特に工夫がなければ、ユーザー名はnginxと一致させておく。)
gunicorn.service[Unit] Description=gunicorn daemon Requires=gunicorn.socket After=network.target [Service] Type=notify # the specific user that our service will run as # ここのユーザ名とグループ名はデーモンを実行するユーザを入れる。 # 特に何も考えていなければec2-userにする。 User=ec2-user Group=ec2-user # another option for an even more restricted service is # DynamicUser=yes # see http://0pointer.net/blog/dynamic-users-with-systemd.html RuntimeDirectory=gunicorn # Djangoプロジェクトのパスを入れる WorkingDirectory=/home/ec2-user/mysite # gunicornの実行ファイルのあるパスと、プロジェクト名.wsgi ExecStart=/usr/local/bin/gunicorn mysite.wsgi ExecReload=/bin/kill -s HUP $MAINPID KillMode=mixed TimeoutStopSec=5 PrivateTmp=true [Install] WantedBy=multi-user.targetgunicorn.socket[Unit] Description=gunicorn socket [Socket] # sockファイルを作る場所を入れる。基本はdjangoプロジェクトのパスを入れておく。 ListenStream=/home/ec2-user/mysite/mysite.sock # Our service won't need permissions for the socket, since it # inherits the file descriptor by socket activation # only the nginx daemon will need access to the socket # gunicorn.serviceと同じユーザを入れる SocketUser=ec2-user # Optionally restrict the socket permissions even more. # SocketMode=600 [Install] WantedBy=sockets.targetここで自分が間違えたポイントとして、sockファイルとsocketファイルを混同させないようにすること。sockファイルはnginxとgunicornがやり取りする際に使うファイルである。
終わったら、gunicornのデーモンの起動と自動起動の登録をする。
gunicorn起動と自動起動設定$ sudo systemctl start gunicorn.service $ sudo systemctl enable gunicorn # ちゃんとプロセスが動いていることを確認する。 $ sudo systemctl status gunicornプロセスが正常に起動していれば、次に進む。
nginxの設定
まず、nginxが正常に動いていて、ネットからつながることを確認する。つながらない場合は、EC2ダッシュボードのインスタンス→セキュリティからポート80が開放されているか、あるいはEC2のダッシュボード上で示されているパブリックIPアドレスやドメインが正しいか確認する。
nginxsystemctl start nginx # コマンド実行後にEC2のパブリックIPアドレスにブラウザからアクセスして、 # nginxが動いているページが見えることを確認する。 # 確認できたら、以下のコマンドで一旦終了する。 systemctl stop nginx確認できたら、nginxの設定をする。設定するファイルは次の通り。(mysite.confは新規作成する。また、拡張子が.confなら好きな名前でOK)
- /etc/nginx/nginx.conf
- /etc/nginx/conf.d/mysite.conf
nginx.conf# 全文は多すぎるので省略。userをnginxからec2-userなどのサービスを動かすユーザーへ変える。 # 思い入れがなければ、gunicorn起動時のユーザと一致させておく ... user ec2-user; ... # httpブロック内などに、この一行があるか確認する。書いてあればOK include /etc/nginx/conf.d/*.conf; ...mysite.confserver { listen 80; # ドメインあるいはIPアドレスを設定する server_name *.*.*.*; # djangoの静的ファイルの置き場所を指定する。 location /static { alias /home/ec2-user/mysite/static; } # djangoのadmin用の静的ファイルの置き場所を指定する。 location /static/admin { alias /home/ec2-user/mysite/static/admin; } # gunicorn起動時に作成されるsockファイルの場所を指定する。 location / { proxy_pass http://unix:/home/ec2-user/mysite/mysite.sock; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }作成したら、デーモンをstart(あるいはrestart)させる。statusでプロセスが問題なく起動していれば次に進む。
nginxsudo systemctl start nginx # すでに起動していた場合はsudo systemctl restart nginx # プロセスが適切に動いていることを確認する sudo systemctl status nginxブラウザから管理者画面を確認してみる
以下コマンドでnginxとgunicornのプロセスがバックグラウンドで動いていることを確認する。動いていなければエラーから各設定を修正してstartしておく。
プロセスのチェック$ sudo systemctl status nginx $ sudo systemctl status gunicornそのあと、自身のDjangoプロジェクトフォルダ(/home/ec2-user/mysite)内にmysite.sockが作成されていることを確認し、ブラウザで「自身のパブリックIPアドレスorドメイン/admin」で管理者画面を開いて見る。CSSを含めてAdminのログインページが表示されれば完了。
もしCSSが死んでいる場合はDjangoの設定でSTATIC_ROOTを忘れていたり、mysite.confのLocationやAliasを疑うといいかも。
また、もし502エラー(Bad Gateway)が出てくるようであれば、nginx.confやgunicorn.serviceのユーザーが同じではなかった(意図した権限を持ったユーザじゃなかった)という点や、同ファイルにおいてsockファイルへのパスが異なっている可能性を疑ってみてもいいかも。
今日はここまで。
- 投稿日:2020-09-28T23:27:09+09:00
何も考えずにEC2にDjangoプロジェクトを展開してブラウザから管理者画面を表示する
はじめに
AWSのEC2上でDjangoプロジェクトを展開しようとして丸一日悩んでしまったので、手順をまとめました。あくまでも自己責任でお願いします。
やったこと
EC2インスタンスを立てて何も考えずにDjangoを動かして、ネット上からアクセスできるようにする。
今回はSQLite3,nginx,gunicornを使います。注意
- ec2-userをそのまま使っていたり、セキュリティ的に怪しい所があるため注意すること。今回はAWSでdjangoプロジェクトをとりあえず動かせるようにすることを目的とする。
- VPC、サブネット、インターネットゲートウェイの設定はすでに完了していて、EC2自体はネットから参照できる状態になっている所からスタートする
- ssh(22)、HTTP(80)とDjango用(8000)のポートが開放されている状態を前提とする
- DBはSQLite3を使用する。また、SQLite3のバージョンを最新にするため既存のソフトウェアでSQLite3を使用している際は注意すること。
- Djangoの管理者画面がスタイルシートなどを含めて読み込めたところで本記事のゴールとする
参考にした文献
- 【20分でデプロイ】AWS EC2にDjango+PostgreSQL+Nginx環境を構築してササッと公開
- AWSにDjangoアプリケーションをデプロイ(Nginx, gunicorn, postgresql)
- AWS Amazon Linux2 で Django 2.2以降の環境を構築する
- 【AWS】EC2でWebサーバの構築 – Nginx構築 -【Nginx】
- Nginxで躓いたところ及びメモ
必要なものを揃える
本記事ではvenvなどは使わず、そのままpython3を使っていくため注意すること。
install$ sudo yum update $ sudo yum install python3 # Amazon Linux 2で$sudo yum install nginx と入力するとコマンドを催促されるので、それを入力する。 $ sudo amazon-linux-extras install nginx1 $ sudo pip3 install django $ sudo pip3 install gunicornAmazon Linux 2にもデフォルトでSQLite3がインストールされていたが、Djangoで求めるバージョン(3.8.3以降)にマッチしないため、SQLiteのアプデを実施する。
SQLite3のinstall# 後述のSQLite3インストール時のmakeファイルのビルドに使用する。 $ sudo yum install gcc # 最新版のSQLite3をインストールする。執筆当時は3.33.0が最新だった。(https://www.sqlite.org/download.html) # 展開した後にmakeファイルをビルドしてmake installまで実施する $ wget https://www.sqlite.org/2020/sqlite-autoconf-3330000.tar.gz $ tar zxvf sqlite-autoconf-3330000.tar.gz $ cd sqlite-autoconf-3330000 # makeファイルのビルド $ ./configure --prefix=/usr/local $ sudo make $ sudo make install次に、過去に入っていたSQLite3から現行のSQLite3を使用するように設定を通す。
SQLite3のinstallつづき# すでにインストール済みのsqlite3を確認する $ sudo find / -name sqlite3 # もともとあったやつは多分これ。バージョンが古いことを一応確認しておく。 /usr/bin/sqlite3 --version # 今回インストールしたのは多分これ。バージョンが古いことを一応確認しておく。 /usr/local/bin/sqlite3 --version # 古い方のsqlite3のフォルダ名を変え、また新しい方のsqlite3へパスを設定しておく sudo mv /usr/bin/sqlite3 /usr/bin/sqlite3_old sudo ln -s /usr/local/bin/sqlite3 /usr/bin/sqlite3 # ライブラリへのパスを通しておく sudo touch ld.so.conf/sqlite3.conf sudo echo /usr/local/lib >> sqlite3.conf # 過去のsqlite3へのリンクとディレクトリを削除 $ sudo rm /lib64/libsqlite3.so.0 $ sudo rm -rf /lib64/libsqlite3.so.[過去のバージョン] # ライブラリのパスを更新する sudo ldconfig # 新しいライブラリへのパス(/usr/local/lib)が通っているか確認する $ ldconfig -p | grep sqlite # 一応バージョンも確認する sqlite3 --version最後のバージョン確認で現行のSQlite3のものが出力されれば、次に進む。
Djangoの設定
とりあえずプロジェクトを作る。プロジェクト名は公式ドキュメントでおなじみのmysiteにする。
django-admin# /home/ec2-userなどの任意のユーザのhomeディレクトリへ移動 $ cd /~ $ django-admin startproject mysite今回はadmin画面が見えればOKなので、最低限の設定だけをする。
django-admin$ cd mysite/mysite $ vi settings.pysetting.pyにおいて設定するのは
- 末尾にSTATIC_ROOTを追加(あわせてimport osも追記)
- ALLOWED_HOSTSに自分のパブリックドメインやパブリックIPアドレスを設定
ぐらい。一応全文を載せておく
setting.pyimport os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'hdxr$tlb=r4ns*#rg806lr*=(aa=_gny4k(1tm9n0atev0n5hs' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True # 自分のドメインやパブリックIPアドレスとか ALLOWED_HOSTS = ['*.*.*.*',] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'mysite.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'mysite.wsgi.application' # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR,'static')終わったら、djangoプロジェクトの静的ファイルをまとめたり、
管理者ページに入るまでの設定をしておく。django$ cd ~/mysite # 自身の情報をここで入れておく。パスは忘れないように $ python3 manage.py createsuperuser # STATICファイルをとりまとめておく $ python3 manage.py collectstaticもし、python3 manage.py collectstaticでcheck_sqlite_versionと書かれたエラーを吐いた場合はSQLite3の設定を見直すこと。過去のSQLite3を見に行っている可能性が高い。
Djangoそのものの設定がうまくできているか確認するために、gunicornから動かしてみる。
django# 必ずmanage.pyなどが存在しているフォルダへ移動すること $ cd ~/mysite $ sudo gunicorn mysite.wsgi --bind=0.0.0.0:8000ブラウザでパブリックIPあるいはドメインへアクセスした際に、例のロケットが表示されればOK。確認できたらgunicornをCtrl+Xなどで終了する。
gunicornの設定
以下の設定ファイルを作成(編集)する。
- /etc/systemd/system/gunicorn.service
- /etc/systemd/system/gunicorn.socket
なお、gunicorn.serviceのExecStartのパスはwhichコマンドで確認する。
gunicorn.serviceのExecStartに設定するパスのチェックwhich gunicornユーザー名(グループ名)、WorkingDirectory、ExecStartのwsgiファイル名とListenStreamは各自の環境に揃えておくこと。(特に工夫がなければ、ユーザー名はnginxと一致させておく。)
gunicorn.service[Unit] Description=gunicorn daemon Requires=gunicorn.socket After=network.target [Service] Type=notify # the specific user that our service will run as # ここのユーザ名とグループ名はデーモンを実行するユーザを入れる。 # 特に何も考えていなければec2-userにする。 User=ec2-user Group=ec2-user # another option for an even more restricted service is # DynamicUser=yes # see http://0pointer.net/blog/dynamic-users-with-systemd.html RuntimeDirectory=gunicorn # Djangoプロジェクトのパスを入れる WorkingDirectory=/home/ec2-user/mysite # gunicornの実行ファイルのあるパスと、プロジェクト名.wsgi ExecStart=/usr/local/bin/gunicorn mysite.wsgi ExecReload=/bin/kill -s HUP $MAINPID KillMode=mixed TimeoutStopSec=5 PrivateTmp=true [Install] WantedBy=multi-user.targetgunicorn.socket[Unit] Description=gunicorn socket [Socket] # sockファイルを作る場所を入れる。基本はdjangoプロジェクトのパスを入れておく。 ListenStream=/home/ec2-user/mysite/mysite.sock # Our service won't need permissions for the socket, since it # inherits the file descriptor by socket activation # only the nginx daemon will need access to the socket # gunicorn.serviceと同じユーザを入れる SocketUser=ec2-user # Optionally restrict the socket permissions even more. # SocketMode=600 [Install] WantedBy=sockets.targetここで自分が間違えたポイントとして、sockファイルとsocketファイルを混同させないようにすること。sockファイルはnginxとgunicornがやり取りする際に使うファイルである。
終わったら、gunicornのデーモンの起動と自動起動の登録をする。
gunicorn起動と自動起動設定$ sudo systemctl start gunicorn.service $ sudo systemctl enable gunicorn # ちゃんとプロセスが動いていることを確認する。 $ sudo systemctl status gunicornプロセスが正常に起動していれば、次に進む。
nginxの設定
まず、nginxが正常に動いていて、ネットからつながることを確認する。つながらない場合は、EC2ダッシュボードのインスタンス→セキュリティからポート80が開放されているか、あるいはEC2のダッシュボード上で示されているパブリックIPアドレスやドメインが正しいか確認する。
nginxsystemctl start nginx # コマンド実行後にEC2のパブリックIPアドレスにブラウザからアクセスして、 # nginxが動いているページが見えることを確認する。 # 確認できたら、以下のコマンドで一旦終了する。 systemctl stop nginx確認できたら、nginxの設定をする。設定するファイルは次の通り。(mysite.confは新規作成する。また、拡張子が.confなら好きな名前でOK)
- /etc/nginx/nginx.conf
- /etc/nginx/conf.d/mysite.conf
nginx.conf# 全文は多すぎるので省略。userをnginxからec2-userなどのサービスを動かすユーザーへ変える。 # 思い入れがなければ、gunicorn起動時のユーザと一致させておく ... user ec2-user; ... # httpブロック内などに、この一行があるか確認する。書いてあればOK include /etc/nginx/conf.d/*.conf; ...mysite.confserver { listen 80; # ドメインあるいはIPアドレスを設定する server_name *.*.*.*; # djangoの静的ファイルの置き場所を指定する。 location /static { alias /home/ec2-user/mysite/static; } # djangoのadmin用の静的ファイルの置き場所を指定する。 location /static/admin { alias /home/ec2-user/mysite/static/admin; } # gunicorn起動時に作成されるsockファイルの場所を指定する。 location / { proxy_pass http://unix:/home/ec2-user/mysite/mysite.sock; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }作成したら、デーモンをstart(あるいはrestart)させる。statusでプロセスが問題なく起動していれば次に進む。
nginxsudo systemctl start nginx # すでに起動していた場合はsudo systemctl restart nginx # プロセスが適切に動いていることを確認する sudo systemctl status nginxブラウザから管理者画面を確認してみる
以下コマンドでnginxとgunicornのプロセスがバックグラウンドで動いていることを確認する。動いていなければエラーから各設定を修正してstartしておく。
プロセスのチェック$ sudo systemctl status nginx $ sudo systemctl status gunicornそのあと、自身のDjangoプロジェクトフォルダ(/home/ec2-user/mysite)内にmysite.sockが作成されていることを確認し、ブラウザで「自身のパブリックIPアドレスorドメイン/admin」で管理者画面を開いて見る。CSSを含めてAdminのログインページが表示されれば完了。
もしCSSが死んでいる場合はDjangoの設定でSTATIC_ROOTを忘れていたり、mysite.confのLocationやAliasを疑うといいかも。
また、もし502エラー(Bad Gateway)が出てくるようであれば、nginx.confやgunicorn.serviceのユーザーが同じではなかった(意図した権限を持ったユーザじゃなかった)という点や、同ファイルにおいてsockファイルへのパスが異なっている可能性を疑ってみてもいいかも。
今日はここまで。
- 投稿日:2020-09-28T23:27:09+09:00
何も考えずにEC2にDjango×SQLite3×nginx×gunicornな環境を構築する
はじめに
AWSのEC2上でDjangoプロジェクトを展開しようとして丸一日悩んでしまったので、手順をまとめました。あくまでも自己責任でお願いします。
やったこと
EC2インスタンスを立てて何も考えずにDjangoを動かして、ネット上からアクセスできるようにする。
今回はSQLite3,nginx,gunicornを使います。注意
- ec2-userをそのまま使っていたり、セキュリティ的に怪しい所があるため注意すること。今回はAWSでdjangoプロジェクトをとりあえず動かせるようにすることを目的とする。
- VPC、サブネット、インターネットゲートウェイの設定はすでに完了していて、EC2自体はネットから参照できる状態になっている所からスタートする
- ssh(22)、HTTP(80)とDjango用(8000)のポートが開放されている状態を前提とする
- DBはSQLite3を使用する。また、SQLite3のバージョンを最新にするため既存のソフトウェアでSQLite3を使用している際は注意すること。
- Djangoの管理者画面がスタイルシートなどを含めて読み込めたところで本記事のゴールとする
参考にした文献
- 【20分でデプロイ】AWS EC2にDjango+PostgreSQL+Nginx環境を構築してササッと公開
- AWSにDjangoアプリケーションをデプロイ(Nginx, gunicorn, postgresql)
- AWS Amazon Linux2 で Django 2.2以降の環境を構築する
- 【AWS】EC2でWebサーバの構築 – Nginx構築 -【Nginx】
- Nginxで躓いたところ及びメモ
必要なものを揃える
本記事ではvenvなどは使わず、そのままpython3を使っていくため注意すること。
install# 後述処理でパスワードを求められる可能性があるため設定する $ sudo passwd ec2-user $ sudo yum update $ sudo yum install python3 # Amazon Linux 2で$sudo yum install nginx と入力するとコマンドを催促されるので、それを入力する。 $ sudo amazon-linux-extras install nginx1 $ sudo pip3 install django $ sudo pip3 install gunicornAmazon Linux 2にもデフォルトでSQLite3がインストールされていたが、Djangoで求めるバージョン(3.8.3以降)にマッチしないため、SQLiteのアプデを実施する。
SQLite3のinstall# 後述のSQLite3インストール時のmakeファイルのビルドに使用する。 $ sudo yum install gcc # 最新版のSQLite3をインストールする。執筆当時は3.33.0が最新だった。(https://www.sqlite.org/download.html) # 展開した後にmakeファイルをビルドしてmake installまで実施する $ wget https://www.sqlite.org/2020/sqlite-autoconf-3330000.tar.gz $ tar zxvf sqlite-autoconf-3330000.tar.gz $ cd sqlite-autoconf-3330000 # makeファイルのビルド $ ./configure --prefix=/usr/local $ sudo make $ sudo make install次に、過去に入っていたSQLite3から現行のSQLite3を使用するように設定を通す。
SQLite3のinstallつづき# すでにインストール済みのsqlite3を確認する $ sudo find / -name sqlite3 # もともとあったやつは多分これ。バージョンが古いことを一応確認しておく。 /usr/bin/sqlite3 --version # 今回インストールしたのは多分これ。バージョンが古いことを一応確認しておく。 /usr/local/bin/sqlite3 --version # 古い方のsqlite3のフォルダ名を変え、また新しい方のsqlite3へパスを設定しておく sudo mv /usr/bin/sqlite3 /usr/bin/sqlite3_old sudo ln -s /usr/local/bin/sqlite3 /usr/bin/sqlite3 # ライブラリへのパスを通しておく sudo touch ld.so.conf/sqlite3.conf sudo echo /usr/local/lib >> sqlite3.conf # 過去のsqlite3へのリンクとディレクトリを削除 $ sudo rm /lib64/libsqlite3.so.0 $ sudo rm -rf /lib64/libsqlite3.so.[過去のバージョン] # ライブラリのパスを更新する sudo ldconfig # 新しいライブラリへのパス(/usr/local/lib)が通っているか確認する $ ldconfig -p | grep sqlite # 一応バージョンも確認する sqlite3 --version最後のバージョン確認で現行のSQlite3のものが出力されれば、次に進む。
Djangoの設定
とりあえずプロジェクトを作る。プロジェクト名は公式ドキュメントでおなじみのmysiteにする。
django-admin# /home/ec2-userなどの任意のユーザのhomeディレクトリへ移動 $ cd /~ $ django-admin startproject mysite今回はadmin画面が見えればOKなので、最低限の設定だけをする。
django-admin$ cd mysite/mysite $ vi settings.pysetting.pyにおいて設定するのは
- 末尾にSTATIC_ROOTを追加(あわせてimport osも追記)
- ALLOWED_HOSTSに自分のパブリックドメインやパブリックIPアドレスを設定
ぐらい。一応全文を載せておく
setting.pyimport os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'hogepugefugahogepugefuga...' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True # 自分のドメインやパブリックIPアドレスとか ALLOWED_HOSTS = ['*.*.*.*',] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'mysite.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'mysite.wsgi.application' # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR,'static')終わったら、djangoプロジェクトの静的ファイルをまとめたり、
管理者ページに入るまでの設定をしておく。django$ cd ~/mysite # 自身の情報をここで入れておく。パスは忘れないように $ python3 manage.py createsuperuser # STATICファイルをとりまとめておく $ python3 manage.py collectstaticもし、python3 manage.py collectstaticでcheck_sqlite_versionと書かれたエラーを吐いた場合はSQLite3の設定を見直すこと。過去のSQLite3を見に行っている可能性が高い。
Djangoそのものの設定がうまくできているか確認するために、gunicornから動かしてみる。
django# 必ずmanage.pyなどが存在しているフォルダへ移動すること $ cd ~/mysite $ sudo gunicorn mysite.wsgi --bind=0.0.0.0:8000ブラウザでパブリックIPあるいはドメインへアクセスした際に、例のロケットが表示されればOK。確認できたらgunicornをCtrl+Xなどで終了する。
gunicornの設定
以下の設定ファイルを作成(編集)する。
- /etc/systemd/system/gunicorn.service
- /etc/systemd/system/gunicorn.socket
なお、gunicorn.serviceのExecStartのパスはwhichコマンドで確認する。
gunicorn.serviceのExecStartに設定するパスのチェックwhich gunicornユーザー名(グループ名)、WorkingDirectory、ExecStartのwsgiファイル名とListenStreamは各自の環境に揃えておくこと。(特に工夫がなければ、ユーザー名はnginxと一致させておく。)
gunicorn.service[Unit] Description=gunicorn daemon Requires=gunicorn.socket After=network.target [Service] Type=notify # the specific user that our service will run as # ここのユーザ名とグループ名はデーモンを実行するユーザを入れる。 # 特に何も考えていなければec2-userにする。 User=ec2-user Group=ec2-user # another option for an even more restricted service is # DynamicUser=yes # see http://0pointer.net/blog/dynamic-users-with-systemd.html RuntimeDirectory=gunicorn # Djangoプロジェクトのパスを入れる WorkingDirectory=/home/ec2-user/mysite # gunicornの実行ファイルのあるパスと、プロジェクト名.wsgi ExecStart=/usr/local/bin/gunicorn mysite.wsgi ExecReload=/bin/kill -s HUP $MAINPID KillMode=mixed TimeoutStopSec=5 PrivateTmp=true [Install] WantedBy=multi-user.targetgunicorn.socket[Unit] Description=gunicorn socket [Socket] # sockファイルを作る場所を入れる。基本はdjangoプロジェクトのパスを入れておく。 ListenStream=/home/ec2-user/mysite/mysite.sock # Our service won't need permissions for the socket, since it # inherits the file descriptor by socket activation # only the nginx daemon will need access to the socket # gunicorn.serviceと同じユーザを入れる SocketUser=ec2-user # Optionally restrict the socket permissions even more. # SocketMode=600 [Install] WantedBy=sockets.targetここで自分が間違えたポイントとして、sockファイルとsocketファイルを混同させないようにすること。sockファイルはnginxとgunicornが共有するファイルである。
終わったら、gunicornのデーモンの起動と自動起動の登録をする。
gunicorn起動と自動起動設定$ sudo systemctl start gunicorn.service $ sudo systemctl enable gunicorn # ちゃんとプロセスが動いていることを確認する。 $ sudo systemctl status gunicornプロセスが正常に起動していれば、次に進む。
nginxの設定
まず、nginxが正常に動いていて、ネットからつながることを確認する。つながらない場合は、EC2ダッシュボードのインスタンス→セキュリティからポート80が開放されているか、あるいはEC2のダッシュボード上で示されているパブリックIPアドレスやドメインが正しいか確認する。
nginxsystemctl start nginx # コマンド実行後にEC2のパブリックIPアドレスにブラウザからアクセスして、 # nginxが動いているページが見えることを確認する。 # 確認できたら、以下のコマンドで一旦終了する。 systemctl stop nginx確認できたら、nginxの設定をする。設定するファイルは次の通り。(mysite.confは新規作成する。また、拡張子が.confなら好きな名前でOK)
- /etc/nginx/nginx.conf
- /etc/nginx/conf.d/mysite.conf
nginx.conf# 全文は多すぎるので省略。userをnginxからec2-userなどのサービスを動かすユーザーへ変える。 # 思い入れがなければ、gunicorn起動時のユーザと一致させておく ... user ec2-user; ... # httpブロック内などに、この一行があるか確認する。書いてあればOK include /etc/nginx/conf.d/*.conf; ...mysite.confserver { listen 80; # ドメインあるいはIPアドレスを設定する server_name *.*.*.*; # djangoの静的ファイルの置き場所を指定する。 location /static { alias /home/ec2-user/mysite/static; } # djangoのadmin用の静的ファイルの置き場所を指定する。 location /static/admin { alias /home/ec2-user/mysite/static/admin; } # gunicorn起動時に作成されるsockファイルの場所を指定する。 location / { proxy_pass http://unix:/home/ec2-user/mysite/mysite.sock; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }作成したら、デーモンをstart(あるいはrestart)させる。statusでプロセスが問題なく起動していれば次に進む。
nginxsudo systemctl start nginx # すでに起動していた場合はsudo systemctl restart nginx # プロセスが適切に動いていることを確認する sudo systemctl status nginxブラウザから管理者画面を確認してみる
以下コマンドでnginxとgunicornのプロセスがバックグラウンドで動いていることを確認する。動いていなければエラーから各設定を修正してstartしておく。
プロセスのチェック$ sudo systemctl status nginx $ sudo systemctl status gunicornそのあと、自身のDjangoプロジェクトフォルダ(/home/ec2-user/mysite)内にmysite.sockが作成されていることを確認し、ブラウザで「自身のパブリックIPアドレスorドメイン/admin」で管理者画面を開いて見る。CSSを含めてAdminのログインページが表示されれば完了。
もしCSSが死んでいる場合はこのあたりを疑ってみる。
- Djangoの設定でSTATIC_ROOTを忘れている。
- mysite.confのLocationやAliasが間違っている。
また、もし502エラー(Bad Gateway)が出てくるようであれば、このあたりを間違えているかも。
- nginx.confやgunicorn.serviceのユーザーが同じではなかった(意図した権限を持ったユーザじゃなかった)
- 同ファイルにおいてsockファイルへのパスが異なっている。
今日はここまで。
- 投稿日:2020-09-28T21:05:29+09:00
ドメイン取得とSSL証明書を導入してWEBサーバを公開
はじめに
これからインフラエンジニアを目指す人や契約したサーバを公開したい人にとって
役にたつ資料になればと思い投稿しました。使うものは以下です。・Server:固定GlobalIPアドレス、Apacheインストールを事前に行ってください。
・Freenom:無料でドメインを取得できるサービスを提供。1年間であれば無料でドメインを利用できます。
・Let's Encrypt:90日間有効な無償SSL/TLSサーバー証明書を発行できます。FreenomeにてDomainの取得
1) Freenomにアクセスし、アカウントを作成します。
2) アカウントを作成したら、Services → 登録をクリックします。
3) 新しい無料ドメインを探しますで、取得したいドメイン名を入れます。
4) 取得可能ドメイン一覧が表示されますが無料のやつから「今すぐ入手!」をクリックします。
5) カートに入ったらチェックアウトをクリックします。
6) 登録時に以下画面が表示されます。
・Use DNSを選択します。
・IP Address欄にはサーバのグローバルIPアドレスを入力します。
・Periodには12Months @ Freeをプルダウンより選択します。
7) Continueをクリックします。
8) I have read and agree to the Terms & Conditionsにチェックを入れて、Complete Orderをクリックします。
9) 上部メニューのMy Domainsより、取得したドメインがあることを確認できれば完了です。SSL証明書の導入
サーバにログインし、必要なものをインストールします。
# yum install epel-release # yum install certbot python-certbot-apache次にSSL証明書の導入のためにSSL証明書を作成します。です。
DocumentRootにはApacheで設定でしているDocumentRootを、Domain名には取得したドメイン名を記載して下さい。# certbot certonly --webroot -w [DocumentRoot] -d [Domain名]有効なメールアドレスを入力します。
Saving debug log to /var/log/letsencrypt/letsencrypt.log Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel): メールアドレスを入力
規約に対してAgreeします。
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org ------------------------------------------------------------------------------- Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf. You must agree in order to register with the ACME server at https://acme-v01.api.letsencrypt.org/directory ------------------------------------------------------------------------------- (A)gree/(C)ancel: A
証明書に関することのメールを受け取りたい場合にはYを、そうではなければNを入力します。
------------------------------------------------------------------------------- Would you be willing to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about EFF and our work to encrypt the web, protect its users and defend digital rights. ------------------------------------------------------------------------------- (Y)es/(N)o: N
正しく証明書の作成が行われた場合は、次のように出力されます。
IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/[Domain名]/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/[Domain名]/privkey.pem Your cert will expire on 2020-12-27. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
作成された証明書のファイルを確認します。
# ls /etc/letsencrypt/live/[Domain名]/ cert.pem chain.pem fullchain.pem privkey.pem READMEApacheのssl.confに証明書のパスを記載します。
# cat /etc/httpd/conf.d/ssl.conf SSLCertificateFile /etc/letsencrypt/live/[Domain名]/cert.pem SSLCertificateKeyFile /etc/letsencrypt/live/[Domain名]/privkey.pem SSLCertificateChainFile /etc/letsencrypt/live/[Domain名]/chain.pemApacheを再起動します。
# systemctl restart httpdサイトアクセスしSSL証明書が導入されたことを確認
取得したドメイン名でChromeやFireFoxでhttpsアクセスを行いURLバーの鍵マークをクリックし、
証明書が反映できていることを確認します。
おまけ SSL証明書自動更新設定
おまけですが、Let's Encryptには証明書を自動更新する機能があるので、
それをCRONで実行することで SSL証明書自動更新をすることができます。# crontab -u root -e 00 02 01 * * certbot renew && systemctl restart httpd ※毎月1日の2時に更新チェックを実施最後に
これからインフラエンジニアを目指す人や作ったWEBサーバを公開したい人向けに、
すごく昔やったことを書いてみました。
単なる自分の興味だけでなく、基礎的な内容についても時間があるときに書いてみたいと思います。
- 投稿日:2020-09-28T20:00:07+09:00
RDS Proxy+RDS(MySQL)の接続について
概要
RDSとRDS Proxyの接続の手順やエラーの解消についてメモです。
RDS Proxy(MySQL)について
制約
使えるリージョン
- 米国東部 (バージニア北部)
- 米国東部 (オハイオ)
- 米国西部 (北カリフォルニア)
- 米国西部 (オレゴン)
- アジアパシフィック (ムンバイ)
- アジアパシフィック (ソウル)
- アジアパシフィック (シンガポール)
- アジアパシフィック (シドニー)
- アジアパシフィック (東京)
- カナダ (中部)
- 欧州 (フランクフルト)
- 欧州 (アイルランド)
- 欧州 (ロンドン)
対象RDSのMySQLバージョン
MySQL 5.6/5.7
MySQL 8.0では使用不可ですアクセス
接続対象のデータベースと同一VPCに設置する必要があります。
プロキシはパブリックアクセス出来ないため、Lambdaと接続する場合はLambdaも同じVPCに設置する。ピン留め
RDSProxyでは、接続が特定のクライアントに固定され、セッション終了まで他のクラアントからの接続が行えなくなる状態に陥ることがあり、
これが発生すると接続をうまいことさばいてくれるRDS proxyの意味がなくなってしまいます。発生する条件
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-proxy.html#rds-proxy-pinning
RDS Proxy は、文字セット、照合順序、タイムゾーン、自動コミット、現在のデータベース、SQL モード、および session_track_schema の各設定への変更を追跡します。したがって、これらの変更時に RDS Proxy はセッションをピン留めしません。この場合、RDS Proxy は、これらの設定の値が同じである他のセッションでのみ、接続を再利用します
SET
、SELECT
コマンドでの変数の変更LOCK TABLE
、LOCK TABLES
コマンド- 一時テーブルの作成
等がピン留めの原因になります。
ピン留めを回避するパラメータについては別にまとめたいと思います。
手順
大まかな流れ
VPC・RDS作成する
省略します。
RDSはRDS Proxyの制約に合ったものを作ります。セキュリティグループを作成してRDSに割り当てておきます。(sg-rds)
AWS Secrets Manager でデータベース認証情報を作成
ユーザー名とパスワードの認証情報をSecrets Managerに保存する
usernameとpasswordのフィールドに接続したいRDSの情報と一致するように登録する
AWS Secrets Manager > シークレット > 新しいシークレットを保存する
AWS CLIで作成する場合
$ aws secretsmanager create-secret --name "secret_name" --description "secret_description" --region region_name --secret-string '{"username":"db_user","password":"db_user_password"}'IAMロールの作成
新規作成
RDS > Add Role to Database
ポリシーの作成
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": "secretsmanager:GetSecretValue", "Resource": [ // 複数ある場合は複数 "作成したシークレットのARN", ] }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": "kms:Decrypt", "Resource": "KMSのARN", "Condition": { "StringEquals": { "kms:ViaService": "secretsmanager.us-east-1.amazonaws.com" } } } ] }KMSのARNは特に設定していない場合、
Key Management Service (KMS) > AWS マネージド型キー > aws/secretsmanager
を使用します。RDS Proxyを作成
コンソールより作成
プロキシ識別子 > RDS Proxyに名前を付けます
エンジンの互換性 > 今回はMySQLデータベース > 作成した対象のRDSを選択します
接続プールの最大接続数 > とりあえずデフォルトのままにSecrets Manager シークレット > 先ほど作成したシークレットを設定
IAM ロール > 先ほど作成したIAMロールを設定
IAM認証 > 今回は無効にします
サブネット > RDSに割り当てたサブネットグループと同じ物を選択しますVPCセキュリティグループ > 新規作成。分かりやすい名前を付けます(sg-rdsproxy)
「プロキシを作成」
セキュリティグループ
EC2 > セキュリティグループ > [セキュリティグループ] > インバウンドルールを編集
RDSのセキュリティグループ(sg-rds)の設定で、RDS Proxyのセキュリティグループ(sg-rdsproxy)からのインバウンドを許可します。
MYSQL/Aurora TCP 3306 sg-rdsproxy ステータスが「利用可能」になったら完了です。
「接続不可」になるとき
このコマンドで接続不可の理由をチェック出来きます。
参考:https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-proxy.html#rds-proxy-verifying$aws rds describe-db-proxy-targets --db-proxy-nameエラー例1
"TargetHealth": { "State": "UNAVAILABLE", "Reason": "PENDING_PROXY_CAPACITY", "Description": "DBProxy Target unavailable due to an internal error" }プロキシがスケーリングオペレーションを完了した後で、接続を再試行します
20分ぐらい待ってから確認してみます。
エラー例2
"TargetHealth": { "State": "UNAVAILABLE", "Description": "DBProxy Target unavailable due to an internal error" }セキュリティグループの設定に問題がないか確認してみる。
エラー例3
"TargetHealth": { "State": "UNAVAILABLE", "Reason": "AUTH_FAILURE", "Description": "Proxy does not have any registered credentials" }IAMロールに問題が無いか確認。
エラー例4
"TargetHealth": { "State": "UNAVAILABLE", "Reason": "AUTH_FAILURE", "Description": "Database authentication failed with provided user credentials" }シークレットの設定に問題が無いか確認。
OK
"TargetHealth": { "State": "AVAILABLE" }
- 投稿日:2020-09-28T19:15:45+09:00
AWS Fargateで動かしてるコンテナの中に入る方法
はじめに
AWS Fargateの場合、トラブルがあった時に動かしてるコンテナの中に入ってなくて調査できないのが不便なのでコンテナに入る方法を調査した。
- 現時点(2020年9月)でAWS Fargateの独自機能でコンテナの中に入る方法はない
- コンテナにsshdをインストールする方法とssm-agentをインストールする方法がある
ssm-agentを使うとSSHのポートの開放やSSHする公開鍵の管理などをしなくて済むためssm-agentを使う方法で行った。
コンテナにsshdをインストールする方法
- SSHのポートを空けておく
- コンテナの中の~/.ssh/authorzied_keysにsshするユーザーの公開鍵を追加しておく
- コンテナの中にsshdをインストールしておいて、コンテナ起動時にsshdを立ち上げておく
メリット
- セッションマネージャーを使わないため、セッションマネージャーを使う料金はかからない
デメリット
- SSHのポートを開放する必要がある
- SSHするユーザーの公開鍵を管理する必要がある
コンテナにssm-agentをインストールする方法
- コンテナ起動時にハイブリッドアクティベーションでアクティベーションを作成して登録してssm-agentを起動
- セッションマネージャーを使ってコンテナの中に入る
メリット
セッションマネージャーを使うので以下のメリットがある。
- SSHのポートを開放する必要がなくなる
- SSHするユーザーの公開鍵を管理する必要がなくなる
デメリット
- 通常のAmazon EC2に対してセッションマネージャーを使う料金はかからないが、ハイブリッドアクティベーションを使って登録することから、オンプレミスインスタンス管理の扱いになるため利用料金がかかる
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/activations.html
Amazon EC2 インスタンスへのアクセスは、追加料金なしでご利用いただけます。
ソースコード
https://github.com/f96q/fargate-ssm-sample
DockerfileとAWS Fargateで動かす環境を作るためのTerraform含む
Dockerfile
Alpineで使う場合はssm-agentのパッケージがないのでソースから持ってきてビルドして設置する必要がある。
他のLinuxディストーションの場合はインストールできるssm-agentのパッケージを提供してる場合があるので、その場合はそのパッケージをインストールするだけで済む。
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-manual-agent-install.htmlARG GOLANG_TAG=1.14.4-alpine3.12 ARG ALPINE_TAG=3.12 # ssm agenet builder FROM golang:$GOLANG_TAG as ssm-agent-builder ARG SSM_AGENT_VERSION=2.3.1205.0 RUN apk add --no-cache \ 'make~=4.3-r0' \ 'git~=2.26.2-r0' \ 'gcc~=9.3.0-r2' \ 'libc-dev~=0.7.2-r3' \ 'bash~=5.0.17-r0' RUN wget -q https://github.com/aws/amazon-ssm-agent/archive/${SSM_AGENT_VERSION}.tar.gz && \ mkdir -p /go/src/github.com && \ tar xzf ${SSM_AGENT_VERSION}.tar.gz && \ mv amazon-ssm-agent-${SSM_AGENT_VERSION} /go/src/github.com/amazon-ssm-agent && \ echo ${SSM_AGENT_VERSION} > /go/src/github.com/amazon-ssm-agent/VERSION WORKDIR /go/src/github.com/amazon-ssm-agent RUN gofmt -w agent && make checkstyle || ./Tools/bin/goimports -w agent && \ make build-linux # merge image FROM alpine:$ALPINE_TAG RUN apk add --no-cache \ 'jq~=1' \ 'aws-cli~=1.18.55-r0' \ 'sudo~=1.9.0-r0' RUN adduser -D ssm-user && \ echo "Set disable_coredump false" >> /etc/sudo.conf && \ echo "ssm-user ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ssm-agent-users && \ mkdir -p /etc/amazon/ssm COPY --from=ssm-agent-builder /go/src/github.com/amazon-ssm-agent/bin/linux_amd64/ /usr/bin COPY --from=ssm-agent-builder /go/src/github.com/amazon-ssm-agent/bin/amazon-ssm-agent.json.template /etc/amazon/ssm/amazon-ssm-agent.json COPY --from=ssm-agent-builder /go/src/github.com/amazon-ssm-agent/bin/seelog_unix.xml /etc/amazon/ssm/seelog.xml COPY docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["amazon-ssm-agent"]docker-entrypoint.sh#!/bin/sh set -e AWS_REGION=${AWS_REGION:-} SSM_ACTIVATION=$(aws ssm create-activation --default-instance-name "fargate-ssm" --iam-role "service-role/AmazonEC2RunCommandRoleForManagedInstances" --registration-limit 1 --region $AWS_REGION) export SSM_ACTIVATION_CODE=$(echo $SSM_ACTIVATION | jq -r .ActivationCode) export SSM_ACTIVATION_ID=$(echo $SSM_ACTIVATION | jq -r .ActivationId) amazon-ssm-agent -register -code $SSM_ACTIVATION_CODE -id $SSM_ACTIVATION_ID -region $AWS_REGION exec "$@"使い終わったら手動で行わないといけないこと
https://aws.amazon.com/jp/systems-manager/pricing/#On-Premises_Instance_Management
動かしてる時間課金されてしまうため以下を行う。
- ハイブリッドアクティベーションの削除
- 登録したインスタンスを外す
- 投稿日:2020-09-28T18:22:25+09:00
CloudWatchEvents (EventBridge) + StepFunctionsを使ったCron処理で同時起動を防ぐ方法
コンテナ化やサーバレス化が進み、従来実装していた方法が通用しない場面が出てきています。
その代表的なものの一つがバッチ処理です。
従来はバッチ処理を行うサーバが存在し、crontabコマンドで設定などを行っていました。
しかしコンテナ化、サーバレス化の影響でバッチサーバというものは存在しなくなります。AWS上でサーバレスなバッチ処理システムを構築するための方法の一つにCloudWatchEvents(最近、EventBridgeになりました。以下CWEと略します)を利用する方法があります。
しかし、このCWEにはイベントが多重に発生するという仕様があります。なお、KubernetsのCronJob
やGCPのCloud Scheduler
でも同様の仕様になっています。
バッチに同実行耐性や冪等性を持たせておけば問題ないのですが、長時間バッチや特殊処理を行うバッチ、作られたのが古くて手を入れづらいバッチなどはなかなか簡単にはいきません。概要
今回は、CWE + StepFuntionsを使ったバッチ処理で同時実行を防止する方法を考えました。
バッチ処理自体はStepFuntionsからLambdaかECS Taskを呼び出して実行することを想定しています。
アーキテクチャとしては、LambdaとDynamoDBを使ったものになります。アーキテクチャ
DynamoDBを利用する理由
排他制御を行うのに重要なのは、ReadとWriteをアトミックに行えることです。
Readとはロックされているかどうか確認する処理であり、Writeとはロックする処理です。ぱっと思いつく利用できそうなやり方としては下記があります。
- Elasticache(Redis)を利用する
- RDS(Mysql)を利用する
RedisではReadとWriteをそのままではアトミックに処理できません。
luaスクリプト書けば出来ますが、ちょっと面倒です。Mysqlは普通にトランザクション利用すればできますが、この処理のためだけに利用するにはちょっと重いです。
そこでDynamoDBを利用します。
DynamoDBはOptimisticLockを利用できるようになっており、サーバレスでスキーマレスなDBであり、利用も料金もかなりお手軽です。StepFunctionsのステートグラフ
こんな感じになると思います。
アンロックはバッチ処理本体の成功失敗に関わらず必ず実行されるようにします。{ "Comment": "CWE+StepFunctionsを利用したバッチ処理の排他制御ステートグラフ", "StartAt": "Lock", "States": { "Lock": { "Type": "Task", "Resource": "ロック関数のARN", "Parameters": { "Key": "hogekey", "Ttl": 300 }, "Catch": [ { "ErrorEquals": ["LockError"], "Next": "Lock Error" }, { "ErrorEquals": ["States.ALL"], "Next": "Other Lock Error" } ], "ResultPath": "$.Lock", "Next": "Lock Ok" }, "Lock Ok": { "Type": "Pass", "Next": "Run" }, "Other Lock Error": { "Type": "Pass", "Next": "Lock Fail" }, "Lock Error": { "Type": "Pass", "Next": "Lock Fail" }, "Lock Fail": { "Type": "Fail", "Cause": "Can not get lock." }, "Run": { "Type": "Task", "Resource": "バッチ処理のARN", "Parameters": { "Command": "hoge" }, "Catch": [ { "ErrorEquals": ["States.ALL"], "Next": "Run Error" } ], "ResultPath": "$.Cron", "Next": "Run Ok" }, "Run Ok": { "Type": "Pass", "Result": { "statusCode": 200 }, "ResultPath": "$.Run", "Next": "UnLock" }, "Run Error": { "Type": "Pass", "Result": { "statusCode": 400 }, "ResultPath": "$.Run", "Next": "UnLock" }, "UnLock": { "Type": "Task", "Resource": "アンロック関数のARN", "Parameters": { "key": "hogekey" }, "ResultPath": "$.UnLock", "Next": "Check Run Result" }, "Check Run Result": { "Type": "Choice", "Choices": [ { "Not": { "Variable": "$.Run.statusCode", "NumericEquals": 200 }, "Next": "Run Fail" } ], "Default": "Run End" }, "Run End": { "Type": "Pass", "End": true }, "Run Fail": { "Type": "Fail", "Cause": "Run failed." } } }DynamoDBスキーマ
{ "Key": "hoge", "Ttl": 300 }
- Key: 文字列
- プライマリーキーです。実行するバッチ処理の名前とか入れておくと良いと思います
- Ttl: 数値
- DynamoDBのttl機能を利用するための項目です
- アンロック処理が失敗した場合の保険です。最大48時間の遅延があります
ロック/アンロック関数
DynamoDBのPutでロックを、Deleteでアンロックを実現します。
実際にPut/Deleteする部分のみ抜粋します。
以下のlambdaコードはnode.jsです。ロック
const DynamoDB = require('aws-sdk/clients/dynamodb'); const ddb = new DynamoDB.DocumentClient({ region: "リージョンを指定", }); function LockError(message) { this.name = 'LockError'; this.message = message; } LockError.prototype = new Error(); const lock = async (key, ttl) => { const nowTime = Math.floor((new Date).getTime()/1000); const ttlTime = nowTime + ttl; // DynamoDBに設定するttlは絶対時間 const params ={ TableName: "LockTable", Item: { Key: key, Ttl: ttlTime, }, ExpressionAttributeNames: { '#k': 'Key', }, ConditionExpression: 'attribute_not_exists(#k)', // 今回の一番の肝になる部分。アトミックにロック確認とロック処理を行える }; try { await ddb.put(params).promise(); } catch (e) { const message = 'Lock Fail.'; throw new LockError(message); // 独自ロックエラーオブジェクト } }; // : // 以下略 // :アンロック
const DynamoDB = require('aws-sdk/clients/dynamodb'); const ddb = new DynamoDB.DocumentClient({ region: "リージョンを指定", }); function UnLockError(message) { this.name = 'UnLockError'; this.message = message; } UnLockError.prototype = new Error(); const unlock = async key => { const params = { TableName: "LockTable", Key: { Key: key }, ExpressionAttributeNames: { '#k': 'Key' }, ConditionExpression: 'attribute_exists(#k)', }; try { await ddb.delete(params).promise(); } catch (e) { const message = 'UnLock Fail.'; throw new UnLockError(message); // 独自アンロックエラーオブジェクト } }; // : // 以下略 // :まとめ
DynamoDBを利用することで簡単に排他制御を実装できました。
しかもDynamoDBは運用が非常に簡単です。
KubernetesやGCPを利用しているとまた違った方法が必要になりますが、ECSやLambdaを運用している場合はこれでなんとかなりそうです。
ただし、連続実行は防げないので、新しく作るバッチ処理はクラウドネイティブを意識して冪等性などを考えながら作っていく必要があります。
- 投稿日:2020-09-28T17:49:40+09:00
AWSとPHPでTwitterOAuthでログイン機能を実装
TWitterOAuthでログイン機能実装が思ったより苦戦したので、エラーになったポイントを伝えていきます。
AWS上でやっていきます。AWSでComposerをインストール
①下記手順を実施してPHPを入れる。(実施済みの方は飛ばす。)
AWS EC2 AmazonLinux2 PHPをインストールする②公式のインストール方法に記載されているコマンドを実行してcomposer本体を取得する。
コマンドは変更になる可能性があるので最新のコマンドは公式ページをご確認してください。php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('sha384', 'composer-setup.php') === '795f976fe0ebd8b75f26a6dd68f78fd3453ce79f32ecb33e7fd087d39bfeb978342fb73ac986cd4f54edd0dc902601dc') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" php composer-setup.php php -r "unlink('composer-setup.php');"③下記コマンドを実行してcomposerの実行ファイルを移動する。
$ sudo mv composer.phar /usr/local/bin/composer④下記コマンドを実行してcomposerの実行ファイルを実行する。
$ php /usr/local/bin/composer⑤下記コマンドを実行してパスを通す。(ユーザ名にはssh接続しているEC2インスタンスのユーザ名を入力する。Apacheのディレクトリルートで実行することを考えフルパスでパスを通す。)
$ echo "export PATH=/home/ec2-user/.config/composer/vendor/bin:$PATH" >> ~/.bash_profile $ source ~/.bash_profileTwitteroauthをインストール
⑥公式Twitteroauthサイトで書いてある通りに実行する。
composer require abraham/twitteroauth⑦vendorのフォルダの中に実行ファイルのautoload.phpがある為、htmlフォルダに移動。
mv vendor /var/www/html/Twitter OAuth認証のPHP実装
⑧動作環境
PHP Version 5.3以上であること(phpinfo();関数で確認)
cURL support enabledであること(phpinfo();関数で確認)
デフォルトで多分、大丈夫です。⑨Callback URLは忘れずに
Twitterアプリケーションの「Settings」→「Application Details」→「Callback URLs」には、「callback.php」のURLを指定します。以下のサンプルコードでは、「'http://' . $_SERVER['HTTP_HOST'] . 'callback.php」が「Callback URLs」に指定されています。⑩config.php
<?php //アプリケーションの Consumer Key と Consumer Secret $sTwitterConsumerKey = '***********************************'; //Consumer Key (API Key) $sTwitterConsumerSecret = '***********************************'; //Consumer Secret (API Secret) //アプリケーションのコールバックURL $sTwitterCallBackUri = 'http://' . $_SERVER['HTTP_HOST'] . '/callback.php'; //コールバックURL //変数初期化 $objTwitterConection = NULL; //TwitterOAuthクラスのインスタンス化 $aTwitterRequestToken = array(); //リクエストトークン $sTwitterRequestUrl = ''; //認証用URL $objTwitterAccessToken = NULL; //アクセストークン $objTwUserInfo = NULL; //ユーザー情報 ?>⑪login.php
<?php ############################################## ### 初期設定 //エラー詳細 ini_set('display_errors', 1); //セッションスタート session_start(); //文字セット header("Content-type: text/html; charset=utf-8"); //インクルード require_once(__DIR__ . '/config.php'); require_once(__DIR__ . '/vendor/autoload.php'); //インポート use Abraham\TwitterOAuth\TwitterOAuth; ############################################## ### twitter oauth request token 取得 //TwitterOAuthクラスをインスタンス化 $objTwitterConection = new TwitterOAuth($sTwitterConsumerKey, $sTwitterConsumerSecret); //oauthリクエストトークンの取得 $aTwitterRequestToken = $objTwitterConection->oauth('oauth/request_token', array('oauth_callback' => $sTwitterCallBackUri)); //oauthリクエストトークンをセッションに格納 $_SESSION['twOauthToken'] = $aTwitterRequestToken['oauth_token']; $_SESSION['twOauthTokenSecret'] = $aTwitterRequestToken['oauth_token_secret']; ############################################## ### twitter 認証へ //Twitter認証URLの作成 $sTwitterRequestUrl = $objTwitterConection->url('oauth/authenticate', array('oauth_token' => $_SESSION['twOauthToken'])); //Twitter認証画面へリダイレクト header('location: '.$sTwitterRequestUrl); ?>⑫logout.php
<?php ############################################## ### 初期設定 //セッションスタート session_start(); //文字セット header("Content-type: text/html; charset=utf-8"); //セッション変数を全て解除 $_SESSION = array(); $_COOKIE = array(); //クッキー削除 setcookie("PHPSESSID", '', time() - 1800, '/'); //セッションを破棄する session_destroy(); ?> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>タイトル</title> <meta http-equiv="Content-Style-Type" content="text/css"> </head> <h2>Twitter アカウント ログアウト</h2> <?php echo "ログアウトしました。"; echo "<a href='https://wepicks.net/code-example/twitter-restapi/login/login.php'>ログインへ</a>"; ?> </body> </html>⑬member.php
<?php ############################################## ### 初期設定 //セッションスタート session_start(); //文字セット header("Content-type: text/html; charset=utf-8"); //インクルード require_once(__DIR__ . '/config.php'); require_once(__DIR__ . '/vendor/autoload.php'); //インポート use Abraham\TwitterOAuth\TwitterOAuth; ############################################## ### アクセストークン確認 if(empty($_SESSION['twAccessToken'])){ echo 'error access token!!'; exit; } ############################################## ### ユーザー情報の取得 //TwitterOAuthクラスをインスタンス化 $objTwitterConection = new TwitterOAuth ( $sTwitterConsumerKey, $sTwitterConsumerSecret, $_SESSION['twAccessToken']['oauth_token'], $_SESSION['twAccessToken']['oauth_token_secret'] ); //ユーザー情報を取得 $objTwUserInfo = $objTwitterConection->get("account/verify_credentials"); ?> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>タイトル</title> <meta http-equiv="Content-Style-Type" content="text/css"> </head> <h2>Twitter アカウント ログイン完了!</h2> <?php echo $_SERVER['REQUEST_URI']; ?><br/> <a href="logout.php">ログアウト</a> <pre> <?php var_dump($_SESSION['twAccessToken']); ?> </pre> <pre> <?php var_dump($objTwUserInfo); ?> </pre> </body> </html>⑭callback.php
<?php ############################################## ### 初期設定 //セッションスタート session_start(); //文字セット header("Content-type: text/html; charset=utf-8"); //インクルード require_once(__DIR__ . '/config.php'); require_once(__DIR__ . '/vendor/autoload.php'); //インポート use Abraham\TwitterOAuth\TwitterOAuth; ############################################## ### oauthトークン確認 if(empty($_SESSION['twOauthToken']) || empty($_SESSION['twOauthTokenSecret']) || empty($_REQUEST['oauth_token']) || empty($_REQUEST['oauth_verifier'])){ echo 'error token!!'; exit; } if($_SESSION['twOauthToken'] !== $_REQUEST['oauth_token']) { echo 'error token incorrect!!'; exit; } ############################################## ### アクセストークン作成 //取得したoauthトークンでTwitterOAuthクラスをインスタンス化 $objTwitterConection = new TwitterOAuth ( $sTwitterConsumerKey, $sTwitterConsumerSecret, $_SESSION['twOauthToken'], $_SESSION['twOauthTokenSecret'] ); //アクセストークンの取得 $_SESSION['twAccessToken'] = $objTwitterConection->oauth("oauth/access_token", array("oauth_verifier" => $_REQUEST['oauth_verifier'])); //メンバーページへリダイレクト header('location: member.php');⑮確認
完成したのか、確認をします。
login.phpにアクセスしてみます。
こんなになってたら、完成です。
参照させていただきました。
PHP で Twitter API OAuth 認証 「ログイン」
twitteroauthのインストール方法
AWS EC2 AmazonLinux2 composerをインストールする
- 投稿日:2020-09-28T16:22:02+09:00
[AWS]EC2,RDS,Djangoを使ってみた。1から環境構築
はじめに
今回初めてバックエンドを触ることになり、かなり手惑いました。
次回作成時この記事を見ればすぐに作成できるようにと思い、自分用または同じように手惑っている方の為に作成しました。初心者ですので間違っている箇所や、考え方がおかしい場所があるかもしれません。その際は、コメント欄で教えていただけると幸いです。
$ <- 自分のPCターミナルでのコマンド [ec2-user] $ <- EC2にログイン中でのコマンド MySQL > <- MySQLにログイン中でのコマンド # <- 私のコメント >>> <- 実行結果(出力値)前提条件
- AWSアカウント作成済
[1] AWS環境設定
[1.1] VPC作成
VPCとはユーザー専用のプライベートなクラウド環境を提供するサービスのことです。
例えば、EC2同士を内部的に通信したい場合や、RDSとやり取りをしたり、内部と外部のネットワークを繋いだりと、多くのAWSはVPCを利用しています。作成手順
- [1] VPCの作成
- [2] 名前タグを「VPCtest」
- [3] IPv4 CIDR ブロックを 「10.0.0.0/16」
- [4] タグキーを「Name」 値を「VPCtest」 とする
これでVPCの作成が完了です。
[1.1.2]サブネット作成
サブネットは最初にVPCによって作られているCIDRブロックを分割したネットワーク群のことです。 サブネットは、VPCの上限を超えなければ、いくらでも作ることが可能です。 サブネットには、主にパブリックサブネットとプライベートサブネットがあります。
最初はイメージがつきにくいと思うので...
広大な地球というインターネット空間に自宅というVPCを作りました。
そこに風呂や寝室など役割の違う部屋(サブネット)を作ったというイメージです。
まちがった考え方だったらごめんなさい(;´Д`)パブリックサブネットとプライベートサブネットを作成します。
パブリックサブネットにはEC2を
プライベートサブネットにはRDSを入れます。
RDSには異なるアベイラビリティゾーンを設定する必要がある為プライベートサブネットは2つ作成します。作成手順
- サブネットの作成
- [1] パブリックtest作成
- [1.1] VPCには先ほど作成した 「VPCtest」 を入れる
- [1.2] アベイラビリティゾーンはとりあえず 「1a」 を選択
- [1.3] IPv4 CIDRブロックは「10.0.1.0/24」
- [2] プライベートtest作成
- [2.1] [1.1][1.2]と同じ
- [2.2] IPv4 CIDRブロックは「10.0.2.0/24」
- [3] プライベートtest2作成
- [3.1] [1.1]と同じ
- [3.2] アベイラビリティゾーンは「1c」を選択([2.1]と異なるアベイラビリティゾーンならOK)
- [3.3] IPv4 CIDRブロックは「10.0.200.0/24」
これでサブネットの設定は完了です。
[1.1.3]ルートテーブル作成
ルートテーブルとは、サブネット内にあるインスタンス等がどこに通信にいくかのルールを定めたものです。つまり、ルートテーブルはパケットの宛先(IPアドレス)を見て、どこに通信を流すかが書かれている表です。この表をみてパケットを運ぶので、表にない宛先のものはパケットを送らないので、通信できません。サブネット毎にどこに通信ができるかを定めたものだというところがポイントです。
作成手順
- [1] ルートテーブルの作成
- [2] 名前タグを 「ルートtest」
- [3] VPCには先ほど作成した 「VPCtest」 を入れる
次にインターネットゲートウェイの作成です。
[1.1.4]インターネットゲートウェイ作成
VPC内からインターネットに接続するためのゲートウェイです。これを使うことで、VPC内のシステムがグローバルIPを使えるようになります。
例えると自宅(VPC領域)に人(インターネット)を入れる玄関のセキュリティ(鍵)の役割です。
作成手順
- [1] インターネットゲートウェイ作成
- [2] 名前タグを 「igw-test」 とする
- [3] タグキーを「Name」 値を「igw-test」 とする
- [4] VPCへアタッチする
- [5] VPCには先ほど作成した 「VPCtest」 を入れる
これでインターネットゲートウェイの設定は完了です。
[1.1.5]ルートテーブルに外部向けルートを追加
作成手順
- [1] ルートの編集
- [2] ルートの追加を押す
- [3] 送信先は 「0.0.0.0/0」 にする
- [4] ターゲットはInternet Gatewayから今回作成したインターネットゲートウェイを選択
- [5] ルートの保存
これでルートテーブルの設定は完了です。
[1.1.6]サブネットにルートテーブルを紐付け
ルートテーブルの関連付けの編集を行います。
作成手順
- [1] パブリックtestのルートテーブルの関連付けの編集
- [2] ルートテーブルIDを今回作成した 「ルートtest」 に変更
- [3] 保存
[1.2]EC2インスタンス作成
作成手順
- [1] インスタンスを起動
- [2] Amazon Linux2 AMI(HVM),SSD Volume Type を選択
- [3] 無料利用枠のインスタンスタイプを選択後、次のステップ(詳細の設定)へ
- [4] ネットワークを 「VPCtest」 、サブネットを 「パブリックtest」、 自動割り当てIPを 「有効」 後、次のステップ(ストレージ追加)へ
- [5] 次のステップ(タグの追加)へ
- [6] 別のタグを追加し、タグキーを「Name」 値を「webサーバーtest」 とした後、次のステップ(セキュリティグループ)へ
- [7] 新しいセキュリティグループを作成し、名前を「webtest-sg」 とする
- [7] 新しいキーペアの作成から キーペア名 「test-key」としキーペアのダウンロードを行う
- [8] インスタンスの作成
EC2が起動できたか確認してみましょう
以下のコマンドを打ち込んでください$ cd $ cd (test-key.pemまでのPATH) #意味がわからなければ $ cd Downloads でOKです $ chmod 400 test-key.pem $ ssh -i test-key.pem ec2-user@xxx.xxx.xxx.xxx #xxxにはEC2のパブリックIPv4アドレスが入る __| __|_ ) _| ( / Amazon Linux 2 AMI ___|\___|___| #上記のEC2の文字が表示されれば起動完了です [ec2-user] $ exit #終了これでEC2を起動することができました。
[1.3]RDSの作成
作成手順
- [1] データベースの作成
- [2] エンジンのタイプ :MySQL
- [3] テンプレート :無料利用枠
- [4] DBインスタンス識別子:database-test
- [5] マスターユーザー名 :お好きな名前
- [6] マスターパスワード :お好きなパスワード
- [7] 接続VPC : VPCtest
- [8] 追加の接続設定タップ
- [9] VPCセキュリティグループ :新規作成
- [10] VPCセキュリティグループ名 :dbtest-SG
- [11] データベースの作成
[1.3.2]セキュリティグループ編集
セキュリティグループについてまだ学習量が少なくセキュリティがガバガバな設定かもしれません。
テスト環境のみで使用してみてください。イメージとしては自宅などに65535個(ポート番号)の玄関がありセキュリティグループで許可した玄関だけ鍵が解除されるという感じでしょうか?
- HTTP通信はポート80を使用
- HTTPS通信はポート443を使用
など代表的なポート番号などが存在します。セキュリティグループwebtest-sgの編集を行います
作成手順
- [1] セキュリティグループから「webtest-sg」のインバウンドルールを編集
- [2] ルールを2つ追加する
- [3] 1つはタイプ 「HTTP」、ソース 「0.0.0.0/0」 とする
- [4] もう1つはタイプ 「MYSQL/Aurora」、ソース 「0.0.0.0/0」 とし、ルールを保存
- [5] 「dbtest-SG」のインバウンドルールを編集
- [6] タイプ 「MYSQL/Aurora」、ソース 「0.0.0.0/0」 とし、ルールを保存
(sgを小文字に統一するの忘れてた(;´Д`))
RDSが起動できたか確認してみましょう
以下のコマンドを打ち込んでください$ cd $ cd (test-key.pemまでのPATH) $ ssh -i test-key.pem ec2-user@xxx.xxx.xxx.xxx [ec2-user] $ sudo yum install mysql # 「 Is this ok [y/d/N]: 」 と表示されるので 「y」 を入力 [ec2-user] $ mysql -h DBエンドポイント -u DBマスターユーザー名 -p # パスワードを要求されるので入力(文字は表示されないが打ち込めています)(コピペ可) >>> Welcome to the MariaDB monitor. # が表示されればRDSのMySQLに接続ができた #データベース内一覧を表示(小文字でも可 show database;) MySQL > SHOW databases; #「dbtest」という名前のデータベースを作成 MySQL > CREATE DATABASE dbtest; #終了 MySQL > exitこれでRDSの設定は完了です。
[2]Django環境構築
[2.1]Python3をインストールするための準備
#yumをアップデート [ec2-user] $ sudo yum update #yumでgitをインストールします [ec2-user] $ sudo yum install git -y #次にgithubのリポジトリからpyenvをクローンしてきます [ec2-user] $ git clone https://github.com/yyuu/pyenv.git ~/.pyenv #パスを通してコマンドが打てるようにします [ec2-user] $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile [ec2-user] $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile [ec2-user] $ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile [ec2-user] $ source ~/.bash_profile #pyenvが入ったか確認 [ec2-user] $ pyenv -v >>> pyenv 1.2.20-7-gdd62b0d1[2.1.2]Python3のインストール
現在入っているPythonのバージョンの確認
Python 2系 は2020年1月1日にサービスを終了した
Python2系について詳しくはこの記事から#Pythonのバージョンを確認 [ec2-user] $ python --version >>> Python 2.7.18 #Python3に移行する #必要な依存関係のインストール [ec2-user] $ sudo yum install gcc zlib-devel bzip2 bzip2-devel readline readline-devel sqlite sqlite-devel openssl openssl-devel -y >>>(略) 完了しました! [ec2-user] $ sudo yum install libffi-devel #現在ダウンロードできるPythonのバージョンを確認 [ec2-user] $ pyenv install -l >>>(略) 3.8.5 3.8.6 (略) #現時点(R2,9/26)での最新版3.8.6をインストール [ec2-user] $ pyenv install 3.8.6 >>> Downloading Python-3.8.6.tar.xz... -> https://www.python.org/ftp/python/3.8.6/Python-3.8.6.tar.xz Installing Python-3.8.6... #少し時間がかかります Installed Python-3.8.6 to /home/ec2-user/.pyenv/versions/3.8.6 #Pythonのバージョン2系から3系へ切り替え [ec2-user] $ pyenv global 3.8.6 [ec2-user] $ pyenv rehash [ec2-user] $ python --version >>> Python 3.8.6 #後に必要になってくるパッケージ [ec2-user] $ sudo yum install python-devel mysql-devel [ec2-user] $ pip install mysqlclient #以上でPython3への移行が完了した[2.2]Djangoの環境構築
pipはEC2(Linux2)に元から入っている
#pipバージョンの確認 [ec2-user] $ pip -V >>> pip 20.2.1 from /home/ec2-user/.pyenv/versions/3.8.6/lib/python3.8/site-packages/pip (python 3.8) #Djangoをインストール [ec2-user] $ pip install django #もし以下のエラーが出たら内容にしたがってシングルクォーテーション('xxxx')内をコピペして再度Djangoをインストールしてください >>> WARNING: You are using pip version 20.2.1; however, version 20.2.3 is available. You should consider upgrading via the 'xxxx' command. #Djangoのバージョン確認 [ec2-user] $ python -m django --version #「test」という名前のプロジェクトを作る [ec2-user] $ django-admin startproject testDjango 確認すると生成されているはずである [ec2-user] $ ls >>> mysite/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py
- トップのmysite・・・ルートディレクトリで任意の名前で作成できる。変更も可。
- manage.py・・・Djangoプロジェクトの様々な操作を行うためのコマンドラインユーティリティ。
- mysite・・・このプロジェクトのパッケージ。
- mysite/init.py・・・このディレクトリがpythonであることの空ファイル。
- mysite/settings.py・・・プロジェクトの設定ファイル。
- mysite/urls.py・・・URLを宣言。
- mysite/asgi.py・・・プロジェクトを提供するASGI互換WEBサーバーのエントリポイント。
- mysite/wsgi.py・・・プロジェクトをサーブするためのWSGI互換WEBサーバーのエントリポイント。
[2.3]FileZillaをダウンロードする
testDjangoのコードを編集するためにSSHで直接vimを使って編集もできるが今回はFailZilaを使ってみようと思う
超初心者向け!FileZilla(ファイルジラ)の使い方Win版FileZilaダウンロード
Mac版FileZilaダウンロードインストールして開き,左上のサーバーボタンを押すと以下の画面が現れる
設定手順
- [1] プロトコルは 「SFTP」 を選択 (FTPでは通信を暗号化していないため)
- [2] ホストは 「EC2のオープンIPアドレス」 を入力
- [3] ログオンタイプは 「鍵ファイル」
- [4] ユーザー 「EC2にログインする時の名前」
- [5] 鍵ファイル 「EC2にログインする時の鍵のPATH」
- [6] ポート は空白でOK
- [7] 接続
設定が完了したのち、上記の画面が現れる
左はローカルサイト(自分のPC内)
右はリモートサイト(EC2内)
ここでファイルの受け渡しや書き換えなどが行える。
とりあえず作ったtestDjangoをローカルサイトに複製をしておく編集したいファイルを右クリック後、表示編集で編集が行える
次の[2.4]でPyCharmで編集できるようにする[2.4]PyCharmをダウンロードする
PyCharmを関連つけるには
- [1] Finderから.pyのファイルを右クリック
- [2] このアプリケーションで開くをPyCharmに設定
- [3] すべてを変更
[2.5]Django設定
[ec2-user] $ cd [ec2-user] $ cd testDjango [ec2-user] $ python manage.py startapp pollssettings.py... (略) ... # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': BASE_DIR / 'db.sqlite3', # } # } DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbtest', 'USER': 'DBマスターユーザー', 'PASSWORD': 'DBマスターユーザーパスワード', 'HOST': 'DBエンドポイント', 'PORT': '3306', } } ... (略) ... #LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'ja' # TIME_ZONE = 'UTC' TIME_ZONE = 'Asia/Tokyo'FileZillaでアップロード
[ec2-user] $ cd [ec2-user] $ cd testDjango [ec2-user] $ python manage.py migrate #ここでエラーが起きた場合考えられる原因は以下に記します。 [ec2-user] $ python manage.py dbshellエラー原因
- FileZillaを開いてアップロードしたか
- 打ち込み忘れているコマンドはないか
- errorのまま放置したダウンロード失敗のコマンドはないか
- python --version で 3以上になっているか
- [2.1.2]のコマンド打つ順番を守ったか
- setting.pyでNAMEはMySQLで作成したDB名か(DB識別子ではない)
その他
pipenvのinstall時に、No module named '_ctypes'が発生する
CentOS7.2 + MySQL5.6 + Python3 - pip install mysqlclient で mysql_config not found が出るもう無理だと感じた方は1からインスタンスを作り直すか、
EC2インスタンスを選択した状態でアクションを押し、同様のものを起動で
同じ設定のインスタンスが生成されます。以上でEC2,RDS,Djangoの設定が完了しました。
おわりに
今回名前のつけ方が下手すぎました。( ´︵` )
たとえば、ブログの名前がxblogなら
xblog-vpc
xblog-public-subnet
xblog-ec2
xblog-rtb
xblog-ec2-sg
xblog-db-sg
などにした方がわかりやすいかと思います。この記事で誰かの役に立っていただければ幸いです。
次回これにAPIを実装してみようと思います。
参考サイト
以下と本記事の途中に非常に参考にさせていただいたサイトを紹介しています。
ありがとうございました。[1] EC2サーバにPython3環境構築
[2] djangoを用いてWEBアプリケーション開発 ~開発その1~
[3] AWS EC2 + RDS 環境で Django, syncdbするところまで
[4] EC2上のDjangoからAmazon RDSに接続する
[5] AWSのEC2(+RDS(+S3))でDjangoアプリケーション(+MySQL)を公開するまで
[6] (Djangoメモ)データベースにMySQLを設定
[7] MySQLでデータベースを作成しよう
[8] Django2.2で開発サーバー起動時にSQLite3のエラーが出た場合の対応
[9] Vim初心者に捧ぐ実践的入門
[10] AWSのルートテーブルって何よ?VPCとサブネット踏まえて簡単に説明してみる
- 投稿日:2020-09-28T15:40:52+09:00
AWS VPC PrivateLink ~ demo
AWS PrivateLinkを利用して、他のAWS VPCに対して安全にサービスを提供する。
1.環境
AWSTemplateFormatVersion: '2010-09-09' Description: 'For making 3 ec2 on 2 VPC. One VPC is a Client VPC which has 1 ec2. The other is a Provider VPC which has 2 ec2. ' Mappings: AWSInstanceType2Arch: t3.medium: Arch: HVM64 t3.micro: Arch: HVM64 t3.small: Arch: HVM64 t3a.micro: Arch: HVM64 AWSRegionArch2AMI: us-east-1: HVM64: ami-0a887e401f7654935 us-west-2: HVM64: ami-0e8c04af2729ff1bb Outputs: AZ: Description: Availability Zone of the Client EC2 instance Value: !Join - '' - - !GetAtt 'ClientInstance.AvailabilityZone' ClientIP: Description: Client EC2 IPaddress Value: !Join - '' - - !GetAtt 'ClientInstance.PublicIp' ProviderEC2One: Description: 'Web server #1 on ProviderVPC' Value: !Join - '' - - http:// - !GetAtt 'ProviderInstanceOne.PublicIp' ProviderEC2Two: Description: 'Web server #2 on ProviderVPC' Value: !Join - '' - - http:// - !GetAtt 'ProviderInstanceTwo.PublicIp' Parameters: InstanceType: AllowedValues: - t3a.micro - t3.micro - t3.small - t3.medium ConstraintDescription: must be a valid EC2 instance type. Default: t3.micro Description: WebServer EC2 instance type Type: String SSHLocation: AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2}) ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x. Default: '0.0.0.0/0' Description: ' The IP address range that can be used to SSH to the EC2 instances' MaxLength: '18' MinLength: '9' Type: String Resources: AttachGateway: Properties: InternetGatewayId: !Ref 'InternetGateway' VpcId: !Ref 'ClientVPC' Type: AWS::EC2::VPCGatewayAttachment ClientInstance: Properties: ImageId: !FindInMap - AWSRegionArch2AMI - !Ref 'AWS::Region' - !FindInMap - AWSInstanceType2Arch - !Ref 'InstanceType' - Arch InstanceType: !Ref 'InstanceType' NetworkInterfaces: - AssociatePublicIpAddress: 'true' DeleteOnTermination: 'true' DeviceIndex: '0' GroupSet: - !Ref 'InstanceSecurityGroup' SubnetId: !Ref 'ClientSubnet' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: ClientEC2 UserData: !Base64 Fn::Join: - '' - - "#!/bin/bash -xe\n" - "yum update -y\n" - "\n" Type: AWS::EC2::Instance ClientSubnet: Properties: AvailabilityZone: us-west-2c CidrBlock: 10.100.0.0/24 MapPublicIpOnLaunch: 'true' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: ClientPublic VpcId: !Ref 'ClientVPC' Type: AWS::EC2::Subnet ClientVPC: Properties: CidrBlock: 10.100.0.0/16 EnableDnsHostnames: 'true' EnableDnsSupport: 'true' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: ClientVPC Type: AWS::EC2::VPC InboundResponsePortsNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'false' NetworkAclId: !Ref 'NetworkAcl' PortRange: From: '1024' To: '65535' Protocol: '6' RuleAction: allow RuleNumber: '102' Type: AWS::EC2::NetworkAclEntry InboundSSHNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'false' NetworkAclId: !Ref 'NetworkAcl' PortRange: From: '22' To: '22' Protocol: '6' RuleAction: allow RuleNumber: '101' Type: AWS::EC2::NetworkAclEntry InstanceSecurityGroup: Properties: GroupDescription: Enable SSH access via port 22 SecurityGroupIngress: - CidrIp: !Ref 'SSHLocation' FromPort: '22' IpProtocol: tcp ToPort: '22' VpcId: !Ref 'ClientVPC' Type: AWS::EC2::SecurityGroup InternetGateway: Properties: Tags: - Key: Application Value: !Ref 'AWS::StackId' Type: AWS::EC2::InternetGateway NetworkAcl: Properties: Tags: - Key: Application Value: !Ref 'AWS::StackId' VpcId: !Ref 'ClientVPC' Type: AWS::EC2::NetworkAcl OutBoundHTTPNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'true' NetworkAclId: !Ref 'NetworkAcl' PortRange: From: '80' To: '80' Protocol: '6' RuleAction: allow RuleNumber: '100' Type: AWS::EC2::NetworkAclEntry OutBoundResponsePortsNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'true' NetworkAclId: !Ref 'NetworkAcl' PortRange: From: '1024' To: '65535' Protocol: '6' RuleAction: allow RuleNumber: '102' Type: AWS::EC2::NetworkAclEntry ProviderAttachGateway: Properties: InternetGatewayId: !Ref 'ProviderInternetGateway' VpcId: !Ref 'ProviderVPC' Type: AWS::EC2::VPCGatewayAttachment ProviderInboundHTTPNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'false' NetworkAclId: !Ref 'ProviderNetworkAcl' PortRange: From: '80' To: '80' Protocol: '6' RuleAction: allow RuleNumber: '100' Type: AWS::EC2::NetworkAclEntry ProviderInboundResponsePortsNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'false' NetworkAclId: !Ref 'ProviderNetworkAcl' PortRange: From: '1024' To: '65535' Protocol: '6' RuleAction: allow RuleNumber: '102' Type: AWS::EC2::NetworkAclEntry ProviderInboundSSHNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'false' NetworkAclId: !Ref 'ProviderNetworkAcl' PortRange: From: '22' To: '22' Protocol: '6' RuleAction: allow RuleNumber: '101' Type: AWS::EC2::NetworkAclEntry ProviderInstanceOne: Metadata: AWS::CloudFormation::Init: config: files: /etc/cfn/cfn-hup.conf: content: !Join - '' - - "[main]\n" - stack= - !Ref 'AWS::StackId' - "\n" - region= - !Ref 'AWS::Region' - "\n" group: root mode: '000400' owner: root /etc/cfn/hooks.d/cfn-auto-reloader.conf: content: !Join - '' - - "[cfn-auto-reloader-hook]\n" - "triggers=post.update\n" - "path=Resources.ProviderInstanceOne.Metadata.AWS::CloudFormation::Init\n" - 'action=/opt/aws/bin/cfn-init -v ' - ' --stack ' - !Ref 'AWS::StackName' - ' --resource ProviderInstanceOne ' - ' --region ' - !Ref 'AWS::Region' - "\n" - "runas=root\n" /var/www/html/index.html: content: !Join - "\n" - - <img src="https://s3.amazonaws.com/cloudformation-examples/cloudformation_graphic.png" alt="AWS CloudFormation Logo"/> - <h1>Congratulations, you have successfully launched for AWS Summit Tokyo Handson.</h1> group: root mode: '000644' owner: root packages: yum: httpd: [] services: sysvinit: cfn-hup: enabled: 'true' ensureRunning: 'true' files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.conf httpd: enabled: 'true' ensureRunning: 'true' Properties: ImageId: !FindInMap - AWSRegionArch2AMI - !Ref 'AWS::Region' - !FindInMap - AWSInstanceType2Arch - !Ref 'InstanceType' - Arch InstanceType: !Ref 'InstanceType' NetworkInterfaces: - AssociatePublicIpAddress: 'true' DeleteOnTermination: 'true' DeviceIndex: '0' GroupSet: - !Ref 'ProviderInstanceSecurityGroup' SubnetId: !Ref 'ProviderSubnet' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: Provider_1 UserData: !Base64 Fn::Join: - '' - - "#!/bin/bash -xe\n" - "yum update -y\n" - "yum update -y aws-cfn-bootstrap\n" - '/opt/aws/bin/cfn-init -v ' - ' --stack ' - !Ref 'AWS::StackName' - ' --resource ProviderInstanceOne ' - ' --region ' - !Ref 'AWS::Region' - "\n" - '/opt/aws/bin/cfn-signal -e $? ' - ' --stack ' - !Ref 'AWS::StackName' - ' --resource ProviderInstanceOne ' - ' --region ' - !Ref 'AWS::Region' - "\n" Type: AWS::EC2::Instance ProviderInstanceSecurityGroup: Properties: GroupDescription: Enable SSH access via port 22 SecurityGroupIngress: - CidrIp: !Ref 'SSHLocation' FromPort: '22' IpProtocol: tcp ToPort: '22' - CidrIp: '0.0.0.0/0' FromPort: '80' IpProtocol: tcp ToPort: '80' VpcId: !Ref 'ProviderVPC' Type: AWS::EC2::SecurityGroup ProviderInstanceTwo: Metadata: AWS::CloudFormation::Init: config: files: /etc/cfn/cfn-hup.conf: content: !Join - '' - - "[main]\n" - stack= - !Ref 'AWS::StackId' - "\n" - region= - !Ref 'AWS::Region' - "\n" group: root mode: '000400' owner: root /etc/cfn/hooks.d/cfn-auto-reloader.conf: content: !Join - '' - - "[cfn-auto-reloader-hook]\n" - "triggers=post.update\n" - "path=Resources.ProviderInstanceOne.Metadata.AWS::CloudFormation::Init\n" - 'action=/opt/aws/bin/cfn-init -v ' - ' --stack ' - !Ref 'AWS::StackName' - ' --resource ProviderInstanceOne ' - ' --region ' - !Ref 'AWS::Region' - "\n" - "runas=root\n" /var/www/html/index.html: content: !Join - "\n" - - <img src="https://s3.amazonaws.com/cloudformation-examples/cloudformation_graphic.png" alt="AWS CloudFormation Logo"/> - <h1>Congratulations, you have successfully launched for AWS Summit Tokyo Handson.</h1> group: root mode: '000644' owner: root packages: yum: httpd: [] services: sysvinit: cfn-hup: enabled: 'true' ensureRunning: 'true' files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.conf httpd: enabled: 'true' ensureRunning: 'true' Properties: ImageId: !FindInMap - AWSRegionArch2AMI - !Ref 'AWS::Region' - !FindInMap - AWSInstanceType2Arch - !Ref 'InstanceType' - Arch InstanceType: !Ref 'InstanceType' NetworkInterfaces: - AssociatePublicIpAddress: 'true' DeleteOnTermination: 'true' DeviceIndex: '0' GroupSet: - !Ref 'ProviderInstanceSecurityGroup' SubnetId: !Ref 'ProviderSubnet' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: Provider_2 UserData: !Base64 Fn::Join: - '' - - "#!/bin/bash -xe\n" - "yum update -y\n" - "yum update -y aws-cfn-bootstrap\n" - '/opt/aws/bin/cfn-init -v ' - ' --stack ' - !Ref 'AWS::StackName' - ' --resource ProviderInstanceTwo ' - ' --region ' - !Ref 'AWS::Region' - "\n" - '/opt/aws/bin/cfn-signal -e $? ' - ' --stack ' - !Ref 'AWS::StackName' - ' --resource ProviderInstanceTwo ' - ' --region ' - !Ref 'AWS::Region' - "\n" Type: AWS::EC2::Instance ProviderInternetGateway: Properties: Tags: - Key: Application Value: !Ref 'AWS::StackId' Type: AWS::EC2::InternetGateway ProviderNetworkAcl: Properties: Tags: - Key: Application Value: !Ref 'AWS::StackId' VpcId: !Ref 'ProviderVPC' Type: AWS::EC2::NetworkAcl ProviderOutBoundHTTPNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'true' NetworkAclId: !Ref 'ProviderNetworkAcl' PortRange: From: '80' To: '80' Protocol: '6' RuleAction: allow RuleNumber: '100' Type: AWS::EC2::NetworkAclEntry ProviderOutBoundHTTPSNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'true' NetworkAclId: !Ref 'ProviderNetworkAcl' PortRange: From: '443' To: '443' Protocol: '6' RuleAction: allow RuleNumber: '101' Type: AWS::EC2::NetworkAclEntry ProviderOutBoundResponsePortsNetworkAclEntry: Properties: CidrBlock: '0.0.0.0/0' Egress: 'true' NetworkAclId: !Ref 'ProviderNetworkAcl' PortRange: From: '1024' To: '65535' Protocol: '6' RuleAction: allow RuleNumber: '102' Type: AWS::EC2::NetworkAclEntry ProviderRoute: DependsOn: AttachGateway Properties: DestinationCidrBlock: '0.0.0.0/0' GatewayId: !Ref 'ProviderInternetGateway' RouteTableId: !Ref 'ProviderRouteTable' Type: AWS::EC2::Route ProviderRouteTable: Properties: Tags: - Key: Application Value: !Ref 'AWS::StackId' VpcId: !Ref 'ProviderVPC' Type: AWS::EC2::RouteTable ProviderSubnet: Properties: AvailabilityZone: us-west-2c CidrBlock: 10.100.0.0/24 MapPublicIpOnLaunch: 'true' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: ProviderPublic VpcId: !Ref 'ProviderVPC' Type: AWS::EC2::Subnet ProviderSubnetNetworkAclAssociation: Properties: NetworkAclId: !Ref 'ProviderNetworkAcl' SubnetId: !Ref 'ProviderSubnet' Type: AWS::EC2::SubnetNetworkAclAssociation ProviderSubnetRouteTableAssociation: Properties: RouteTableId: !Ref 'ProviderRouteTable' SubnetId: !Ref 'ProviderSubnet' Type: AWS::EC2::SubnetRouteTableAssociation ProviderVPC: Properties: CidrBlock: 10.100.0.0/16 EnableDnsHostnames: 'true' EnableDnsSupport: 'true' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: ProviderVPC Type: AWS::EC2::VPC Route: DependsOn: AttachGateway Properties: DestinationCidrBlock: '0.0.0.0/0' GatewayId: !Ref 'InternetGateway' RouteTableId: !Ref 'RouteTable' Type: AWS::EC2::Route RouteTable: Properties: Tags: - Key: Application Value: !Ref 'AWS::StackId' VpcId: !Ref 'ClientVPC' Type: AWS::EC2::RouteTable SMInstanceProfile: Properties: Path: / Roles: - !Ref 'SMRole' Type: AWS::IAM::InstanceProfile SMRole: Properties: AssumeRolePolicyDocument: Statement: - Action: - sts:AssumeRole Effect: Allow Principal: Service: - ec2.amazonaws.com Version: '2012-10-17' Path: / Policies: - PolicyDocument: Statement: - Action: - ssm:UpdateInstanceInformation - ssmmessages:CreateControlChannel - ssmmessages:CreateDataChannel - ssmmessages:OpenControlChannel - ssmmessages:OpenDataChannel Effect: Allow Resource: '*' - Action: - s3:GetEncryptionConfiguration Effect: Allow Resource: '*' Version: '2012-10-17' PolicyName: root-0317a Type: AWS::IAM::Role SubnetNetworkAclAssociation: Properties: NetworkAclId: !Ref 'NetworkAcl' SubnetId: !Ref 'ClientSubnet' Type: AWS::EC2::SubnetNetworkAclAssociation SubnetRouteTableAssociation: Properties: RouteTableId: !Ref 'RouteTable' SubnetId: !Ref 'ClientSubnet' Type: AWS::EC2::SubnetRouteTableAssociation2.NLBの作成とVPC エンドポイントサービスを設定する
2-1.NLBを作成する
・ロードバランサーの設定
VPCは Provider-VPCを選択
・セキュリティ設定の構成
SSLは利用しないので、そのまま進む。・ルーティングの設定
ターゲットグループ名だけ入力し、後はデフォルトとする。・確認
完了。2-2.VPC エンドポイントサービスを作成する
VPCの「エンドポイントサービスの作成」から「2-1.NLBを作成する」で作成したNLBを紐づける。
3.VPC エンドポイントを作成する
「2.VPC エンドポイントサービス設定を作成し、NLBを紐づける」で作成したエンドポイントサービスをClientPublicのVPCを指定する。
セキュリティグループは、VPC内からのHTTP通信を受け付けるようにする。
4.動作確認
Client側のEC2でEC2 Instance Connectで接続し、エンドポイントにcurlで接続する。
- 投稿日:2020-09-28T13:59:25+09:00
AWS Lambdaも満足に書けないインフラエンジニアがAWS認定DevOpsエンジニアプロフェッショナル (DOP)を受験して合格してみた
AWS Lambdaも満足に書けないインフラエンジニアの身でありながら
AWS認定DevOpsエンジニアプロフェッショナルを受験して合格したので
勉強方法をまとめてみます。受験した人
・AWS歴4年半
・インフラエンジニアではあるが、AWS CLIも満足につかえず基本的にマネジメントコンソール でぽちぽちしている
・AWS CLIも使えないので当然、AWS Lambdaなんて書けるわけもない。
・業務においてAWSにて本番ワークロードで稼働中のサーバ運用や新規構築を担当している。AWS認定試験受験履歴
資格名 取得日 AWS Certified Solutions Architect – Associate (SAA) 2016/ 8/29取得 AWS Certified SysOps Administrator – Associate (SOA) 2017/ 5/21取得 AWS Certified Developer – Associate (DVA) 2018/11/17取得 AWS Certified Solutions Architect – Professional (SAP) 2018/ 2/13取得 今回、受験したDevOpsエンジニアプロフェッショナルの立ち位置は以下になります。
参照:https://aws.amazon.com/jp/certification/今回はSOAの有効期限が近づいてきたので、更新も兼ねてDevOpsを受験してみました。
(ほんとは2020/5/21に失効だったけど、コロナの影響で半年有効期限が伸びたのでギリギリの取得です。)勉強方法
DevOpsエンジニアプロフェッショナルの参考書はでていないので、
セオリー通り以下の勉強を実施しました。・AWSドキュメント熟読
・試験範囲のAWSサービスについてホワイトペーパの熟読
・試験範囲のAWSサービスについてBlackBeltを熟読
・AWS公式のサンプル問題
・AWS公式の模擬試験嘘です・・・・。
ホワイトペーパなんて1分たりとも読んでません。
読んだらヨダレ垂らして寝ちゃいます・・・。勉強方法(真)
ほんとはこっち。
教科書を読むのが苦手なので、勉強は実戦形式で問題をといて
不明点のみBlackbeltやAWSドキュメントを参照しました。・AWS公式デジタルトレーニング
https://aws.amazon.com/jp/certification/certification-prep/
・AWS WEB問題集で学習しよう
https://aws.koiwaclub.com/
・Udemy
https://www.udemy.com/course/aws-devops-dop-c01-2020/
・AWS公式の模擬試験
https://aws.amazon.com/jp/certification/certified-devops-engineer-professional/AWS公式デジタルトレーニングが中でもかなりわかりやすかったです。
音声は英語ですが、日本語字幕で説明してくれて、資料も日本語です。
カルキュラムも日本語で各カテゴリごとポイントを紹介があり、練習問題もあり見やすいです。
全体で7時間とありますが、カテゴリごとに見ていけばそんなに時間がかからないイメージです。受験した感想
「AWS WEB問題集で学習しよう」や「Udemy」で問題集をといていくと問題のパターンがある程度みえてくるので
75問に対し、80分ほどで一通回答が完了し、残りの時間は見直しに費やす予定でした。しかし現実はそう甘くなく、75問解いた段階で、120分使っていました。
そこから回答に迷った問題の見直し、再度問題の1問目から見直しを行う予定でしたが、
時間が足りず、全体の半分ほどしか見直しが行えませんでした。
(やはりProfessionalレベルは時間が厳しい・・・)Professional試験は問題文も選択肢も共に長文が出題される傾向にあるので、
何を問われているかをポイントを見極める訓練が必要かと思いました。まとめ
勉強している最中は、はじめてProfessionalレベルの問題を解いた時よりは絶望感はありませんでしたが、
やはり本番の試験となると苦戦しました。(30問目あたりから・・・。)個人的にはSAPやDVAよりは難易度は簡単に感じました。
ただ、私のようにdev系のナレッジのない人間でも、合格はできましたので、
受験を考えている方は学習方法を参考にしていただければと思います。
- 投稿日:2020-09-28T07:49:41+09:00
MacとSSHでさくっとIPアドレスと国判定を変える
Amazonプライムである作品が「このビデオは、現在、 お住まいの地域では視聴できません」となっていました。ではアメリカからアクセスしたら利用可能になるのか調べてみました。
今回はAWSのLightsailを使い、リージョンをus-east-1にすることで、バージニア州からのアクセスとすることを目標とします。
サーバーと秘密鍵がスタンバイできたら、以下のコマンドでSSHしましょう。ホスト名(IP)と秘密鍵のパス等は置き換えてください。
ssh ec2-user@10.87.131.119 -i ~/Downloads/LightsailDefaultKey-us-east-1.pem -D 10000
もしSSHが失敗し、原因がダウンロードしたての秘密鍵を利用して以下のようなWarningが出たことによるものであったら、
chmod 600 ~/Downloads/LightsailDefaultKey-us-east-1.pem
で解消します。@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: UNPROTECTED PRIVATE KEY FILE! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Permissions 0644 for '/Users/umihico/Downloads/LightsailDefaultKey-us-east-1.pem' are too open. It is required that your private key files are NOT accessible by others. This private key will be ignored.無事SSHが成功している間に、別のターミナルを開いて以下を実行します。
networksetup -setsocksfirewallproxy Wi-fi localhost 10000
このコマンドはMacでWi-Fiを利用中の場合の設定になります。もとに戻したいときは以下のコマンドです。インスタンスの削除も忘れずに。
networksetup -setsocksfirewallproxystate Wi-fi off
ipinfo.ioでも無事に変更が確認できました。
しかし、Amazonプライムの該当作品は引き続き同じ文言で視聴できませんでした\(^o^)/
IP以前にamazon.co.jpである時点で弾くのが正しいですよね。多分そうなっているんでしょう。
- 投稿日:2020-09-28T00:28:10+09:00
ServerlessFramework(AWS)でstage毎にS3などのリソースを別に作る方法
provider > stage:
${opt:stage, self:custom.defaultStage}
custom > defaultStage:dev
custom > resourcePrefix :${self:provider.stage}
を使った値
と設定して
BucketNameを${self:custom.resourcePrefix}-resource
とするserverles..ymlservice: serverless-sample frameworkVersion: '2' provider: name: aws runtime: python3.8 stage: ${opt:stage, self:custom.defaultStage} region: ${opt:provider, self:custom.defaultRegion} custom: author: iwato defaultStage: dev defaultRegion: ap-northeast-1 resourcePrefix: ${self:custom.author}-${self:service}-${self:provider.stage} environment: dev: # 開発環境 key: dev-value test: # テスト環境 key: test-value staging: # 検証環境 key: staging-value prod: # 本番環境 prod: prod-value resources: Resources: NewResource: Type: AWS::S3::Bucket Properties: BucketName: ${self:custom.resourcePrefix}-resource Outputs: NewOutput: Description: "Description for the output" Value: "Some output value"
- 投稿日:2020-09-28T00:16:11+09:00
LineからEC2を操作する
AWS EC2上で自作のアプリケーションを回しているのですが、実行、停止を頻繁に繰り返すことがあり、EC2に入っていちいち操作させるのが結構めんどくさかったりします。
そこで、LineからEC2を操作して、プログラムの実行停止をできるようにしてみます。全体像
以下のフローでLineからEC2を操作します。
1. Line Messaging APIからAPI GatewayへPOSTリクエストを送信
2. API Gatewayをトリガにして、Lambda関数が実行
3. Lambda関数からEC2インスタンスが起動or停止される
4. 起動時には設定したスクリプトが自動実行される
作成手順
- EC2インスタンスの作成
- IAMポリシーの作成
- Lambda関数の設定
- API Gatewayの設定
- Line Messaging APIの設定
- EC2起動時実行スクリプトの設定
1. EC2インスタンスの作成
プログラムを実行するためのEC2インスタンスを作成しておきます。(作成手順は省略)
後述のLambda関数で利用するため、作成したEC2のインスタンスIDをメモしておきます。2. Lambda用IAMロールの作成
今回はLambdaの関数からEC2を操作するため、LambdaにEC2インスタンスの起動停止を行うための、IAMロールを付与してあげます。
こちらのAWS公式マニュアルに従います。【IAMポリシーの作成】
IAM -> ポリシー -> ポリシーの作成 -> JSON
でエディタに下記のJSONを記載します。
ポリシー名はec2StartStopPolicyとします。
ポリシードキュメント
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "ec2:Start*", "ec2:Stop*" ], "Resource": "*" } ] }【IAMロールの作成】
ロール -> ロールの作成 を選択し、エンティティの種類にAWSサービス、ユースケースにLambdaを選択します。
アクセス権限ポリシーで、上で作成したポリシーを選択し、ロール名をec2StartStopRoleとして、ロールを作成します。3. Lambda関数の作成
EC2を起動、停止するためのLambda関数を作成します。
【関数の作成】
Lineから「起動」「停止」というメッセージを受け取って、対応するアクションを行う仕様とします。Lambda -> 関数の作成 -> 一から作成 -> ランタイムにPython3.8を選択 -> 関数の作成
配置されたLambda関数の関数コード内、lambda_function.pyに下記のコードを記載します。
コード内のregionとinstancesには対象のEC2インスタンスの内容に置き換えてください。
Lambda関数
lambda_function.pyimport json import urllib.request, urllib.parse import boto3 import os region = '**リージョン**' instances = ['**インスタンスID**'] ec2 = boto3.client('ec2', region_name=region) def start_ec2(): ec2.start_instances(InstanceIds=instances) def stop_ec2(): ec2.stop_instances(InstanceIds=instances) def lambda_handler(event, context): # TODO implement text = event['events'][0]['message']['text'] if text == '起動': start_ec2() elif text == '停止': stop_ec2() else: pass print("Line Message is {}".format(text)) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }【IAMロールを付与】
作成したIAMロールをLambda関数に付与します。
Lambdaのアクセス権限タブで実行ロールの編集をクリックし、先ほど作成したIAMロールを選択して保存します。
4. API Gatewayの設定
作成したLambdaのデザイナー上から、トリガーを追加を選択し、API Gatewayをトリガーとして追加します。
作成したAPI GatewayのAPIエンドポイントをメモしておきます。
今回は、LineからのPOSTリクエストに応答して、Lambda関数をトリガするようにします。
API Gateway画面から、アクション -> メソッドを追加 -> POSTを選択 -> 作成したLambda関数を指定して、POSTリクエスト用のメソッドを作成します。
最後に、アクション->APIのデプロイ を選択し、デプロイします。
4. Line Messaging APIの設定
API GatewayにPOSTリクエストを送るためのLine Messaging APIを作成します。
Line Developers出ない方は新規登録をしましょう。【チャネルの作成】
Line Developersにログインし、新規Providerを作成します。
Channelsに「Create a MEssaging API channel」を選択し、Channel nameなど適当に埋めて、Channelを作成します。【Webhook URLの設定】
Messaging APIの設定で、Webhook URL選択し、作成したAPI GatewayのAPIエンドポイントを記載します。【動作確認】
Messaging API設定画面で表示されるQRコードを読み取り、Lineで友達追加します。
このチャネルで、「起動」または「停止」を送って、実際にEC2インスタンスが起動、停止することを確かめます。
起動、停止を送ると、、、
5. 起動時設定
最後に、EC2が起動したときに特定のスクリプトを実行できるようにします。
今回は起動・停止時にメッセージを出力するだけの、mytestというスクリプトを登録します。mytest#!/bin/sh # chkconfig: 2345 99 10 # description: test shell case "$1" in start) echo "start!" > /tmp/start.txt ;; stop) echo "stop!" > /tmp/stop.txt ;; *) break ;; esac下記のコマンドでchkconfigに登録すると、スクリプトが実行されるようになります。
$ chkconfig --add mytest $ chkconfig mytest onまとめ
以上で、好きなタイミングでLineからEC2を起動させプログラムを自動起動できるようになりました!外出中でも気軽にEC2を操作できるのが嬉しいですね。
- 投稿日:2020-09-28T00:08:47+09:00
【Python】AWS-CDKを利用してBatch環境を作成する
1. はじめに
今回は、AWS Batchの環境をCDKを利用して実装していきます。
よくTypeScriptでの実装例は多いのですが、Pythonはあまりなかったので記事にしました。1.1 実行環境
実行環境は以下の通りです。
特にインストールやaws-cliとaws-cdkの初期設定については触れません。
ただ、注意点としてaws-cdkは非常にバージョンの更新頻度が高く、現在書かれている内容でも動かない可能性があります。
- MacOS: Catarila (10.15.6)
- Python (brew): 3.8
- aws-cli (brew): 2.0.50
- aws-cdk (brew): 1.63.0 (build 7a68125)
- docker: 19.03.12
1.2 料金
気になるのは、料金ですよね。
以下の条件で動かしたところ、課金されたのはEC2の料金のみで0.01 [$/日]
程度でした。
(Batchだと、毎回ジョブにキューが追加されてからインスタンスの作成が行われ、ジョブが完了すると削除されるためです。)
- 処理時間:10~15 [min/回]
- 起動したインスタンスタイプ: c4.large (スポット料金)
1.3 手順
Batchの実行環境を整えるのに以下の手順で実装を行います。
- Python scriptの作成
- Dockerfileの作成
- ECRに登録
- CDKにてappを記述
1.4 準備
フォルダ構成は以下の通りです。
ファイル名の右側に付いているのは、上記の手順の番号と対応しています。batch_example └── src ├── docker │ ├── __init__.py (1) │ ├── Dockerfile (2) │ ├── requirements.txt (2) │ └── Makefile (3) └── batch_environment ├── app.py (4) ├── cdk.json └── README.md2. 実装
それでは、上記の手順にしたがって実装を進めていきます。
2.1 Python scriptの作成
Docker内にて実行するscriptの例を以下に示します。
click
はCMDからのコマンドライン引数の受け渡しをするために、
watchtower
はCloudWatch Logsへのログの書き込みをするために利用しています。__init__.py# timeのparse用 from datetime import datetime from logging import getLogger, INFO # インストールライブラリ from boto3.session import Session import click import watchtower # envvarで指定すると環境変数から値を取得する @click.command() @click.option("--time") @click.option("--s3_bucket", envvar='S3_BUCKET') def main(time: str, s3_bucket: str): if time: # CloudWatch Eventから実行することを想定し、時刻のparseをする d = datetime.strptime(time, "%Y-%m-%dT%H:%M:%SZ") # 実行日付を取得 execute_date = d.strftime("%Y-%m-%d") # loggerの設定 # loggerの名前がログストリームの名前になる logger_name = f"{datetime.now().strftime('%Y/%m/%d')}" logger = getLogger(logger_name) logger.setLevel(INFO) # CloudWatch Logsのロググループの名前をここで指定 # Sessionを渡してIAM Role経由でログを送信 handler = watchtower.CloudWatchLogHandler(log_group="/aws/some_project", boto3_session=Session()) logger.addHandler(handler) # 実行予定の処理 # ここでは、CloudWatch Logsに実行日時を書き込むのみ logger.info(f"{execute_date=}") if __name__ == "__main__": """ python __init__.py --time 2020-09-11T12:30:00Z --s3_bucket your-bucket-here """ main()2.2 Dockerfileの作成
次に上記のPython scriptを実行するDockerfileを作成します。
ここを参考にマルチステージでビルドしました。Dockerfile# ここはビルド用のコンテナ FROM python:3.8-buster as builder WORKDIR /opt/app COPY requirements.txt /opt/app RUN pip3 install -r requirements.txt # ここからは実行用コンテナの準備 FROM python:3.8-slim-buster as runner COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages COPY src /opt/app/src WORKDIR /opt/app/src CMD ["python3", "__init__.py"]同時にrequirements.txtにも利用するライブラリを入れておきます。
requirements.txtclick watchtower2.3 ECRへの登録
Dockerfileの作成が終わったら、ECRに登録します。
まずは、コンソールからECRに「リポジトリ作成」ボタンを押してrepositoryを作成します。リポジトリの名前は適当に設定します。
作成したリポジトリを選択して、「プッシュコマンドの表示」ボタンを押します。
すると、プッシュに必要なコマンドが表示されるので、何も考えずにコピーして実行していきます。
ここで、失敗する方はAWS CLIの設定がうまくで来ていないと思うので、AWS CLIの設定を見直してください。毎回、コマンドを打つのが大変なので、上記のコマンドをコピーしたMakefileを作成します。
(1のコマンドの--username AWS
は定数だと思われます。)Makefile.PHONY: help help: @echo " == push docker image to ECR == " @echo "type 'make build tag push' to push docker image to ECR" @echo "" .PHONY: login login: (1のコマンド)aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin {ACCOUNT_NUMBER}.dkr.ecr.ap-northeast-1.amazonaws.com .PHONY: build build: (2のコマンド)docker build -t {REPOSITORY_NAME} . .PHONY: tag tag: (3のコマンド)docker tag {REPOSITORY_NAME}:latest {ACCOUNT_NUMBER}.dkr.ecr.ap-northeast-1.amazonaws.com/{REPOSITORY_NAME}:latest .PHONY: push push: (4のコマンド)docker push {ACCOUNT_NUMBER}.dkr.ecr.ap-northeast-1.amazonaws.com/{REPOSITORY_NAME}:latestこのMakefileを利用することで、以下のように簡単にコマンドを短縮できます。
加えて、上記のMakefileには特に外部にもれても危ない情報はないと思うので、ソースコードも共有できます。# ECRにログイン $ make login # ECRに最新の状態のimageをpush $ make build tag push2.4 CDKの実装
CDKの実装内容はTypeScriptで書かれたこの記事を参考に行いました。
なお、事前にapp.pyを実装するディレクトリにて$ cdk init
の実行をした方が良いです。2.4.1 実装に必要なパッケージのインストール
一つ一つのパッケージ名が長いですね・・・。加えて、インストールにかかる時間も結構長いです。
$ pip install aws-cdk-core aws-cdk-aws-stepfunctions aws-cdk-aws-stepfunctions-tasks aws-cdk-aws-events-targets aws-cdk.aws-ec2 aws-cdk.aws-batch aws-cdk.aws-ecr2.4.2 app.pyの実装
まずは、今回構築する環境のクラスを作成します。
BatchEnvironmentクラスの引数として、stack_name
とstack_env
を設定しています。
これは、この環境の名前と、実行環境(検証/開発/本番)などと対応しています。
(なお、本当に実行環境を分ける場合は、ECRのリポジトリも変更する必要があるかなと思います。)app.pyfrom aws_cdk import ( core, aws_ec2, aws_batch, aws_ecr, aws_ecs, aws_iam, aws_stepfunctions as aws_sfn, aws_stepfunctions_tasks as aws_sfn_tasks, aws_events, aws_events_targets, ) class BatchEnvironment(core.Stack): """ Batchの環境とそれを実行するStepFunctions + CloudWatch Event環境を作成 """ # 上で作成したECRのリポジトリ名 # Batchで実行する際に、このリポジトリからイメージをpullする ECR_REPOSITORY_ARN = "arn:aws:ecr:ap-northeast-1:{ACCOUNT_NUMBER}:repository/{YOUR_REPOSITORY_NAME}" def __init__(self, app: core.App, stack_name: str, stack_env: str): super().__init__(scope=app, id=f"{stack_name}-{stack_env}") # 以下の実装はここの下に連なるイメージです。2.4.3 app.pyの実装(VPC環境の作成)
app.py# def __init__(...):の中 # CIDRは好きな範囲を cidr = "192.168.0.0/24" # === # # vpc # # === # # VPCはパブリックサブネットしか利用しない場合は、無料で利用可能できる(はずです) vpc = aws_ec2.Vpc( self, id=f"{stack_name}-{stack_env}-vpc", cidr=cidr, subnet_configuration=[ # Public Subnetのnetmaskを定義 aws_ec2.SubnetConfiguration( cidr_mask=28, name=f"{stack_name}-{stack_env}-public", subnet_type=aws_ec2.SubnetType.PUBLIC, ) ], ) security_group = aws_ec2.SecurityGroup( self, id=f'security-group-for-{stack_name}-{stack_env}', vpc=vpc, security_group_name=f'security-group-for-{stack_name}-{stack_env}', allow_all_outbound=True ) batch_role = aws_iam.Role( scope=self, id=f"batch_role_for_{stack_name}-{stack_env}", role_name=f"batch_role_for_{stack_name}-{stack_env}", assumed_by=aws_iam.ServicePrincipal("batch.amazonaws.com") ) batch_role.add_managed_policy( aws_iam.ManagedPolicy.from_managed_policy_arn( scope=self, id=f"AWSBatchServiceRole-{stack_env}", managed_policy_arn="arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole" ) ) batch_role.add_to_policy( aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, resources=[ "arn:aws:logs:*:*:*" ], actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams" ] ) ) # EC2に付与するRole instance_role = aws_iam.Role( scope=self, id=f"instance_role_for_{stack_name}-{stack_env}", role_name=f"instance_role_for_{stack_name}-{stack_env}", assumed_by=aws_iam.ServicePrincipal("ec2.amazonaws.com") ) instance_role.add_managed_policy( aws_iam.ManagedPolicy.from_managed_policy_arn( scope=self, id=f"AmazonEC2ContainerServiceforEC2Role-{stack_env}", managed_policy_arn="arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" ) ) # S3にアクセスするpolicyの追加 instance_role.add_to_policy( aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, resources=["*"], actions=["s3:*"] ) ) # CloudWatch Logsにアクセスするpolicyの追加 instance_role.add_to_policy( aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, resources=[ "arn:aws:logs:*:*:*" ], actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams" ] ) ) # EC2にロールを付与 instance_profile = aws_iam.CfnInstanceProfile( scope=self, id=f"instance_profile_for_{stack_name}-{stack_env}", instance_profile_name=f"instance_profile_for_{stack_name}-{stack_env}", roles=[instance_role.role_name] )2.4.4 app.pyの実装(Batchの実行環境およびジョブ定義・ジョブキューの作成)
app.py# VPCの続き... # ===== # # batch # # ===== # batch_compute_resources = aws_batch.ComputeResources( vpc=vpc, maxv_cpus=4, minv_cpus=0, security_groups=[security_group], instance_role=instance_profile.attr_arn, type=aws_batch.ComputeResourceType.SPOT ) batch_compute_environment = aws_batch.ComputeEnvironment( scope=self, id=f"ProjectEnvironment-{stack_env}", compute_environment_name=f"ProjectEnvironmentBatch-{stack_env}", compute_resources=batch_compute_resources, service_role=batch_role ) job_role = aws_iam.Role( scope=self, id=f"job_role_{stack_name}-{stack_env}", role_name=f"job_role_{stack_name}-{stack_env}", assumed_by=aws_iam.ServicePrincipal("ecs-tasks.amazonaws.com") ) job_role.add_managed_policy( aws_iam.ManagedPolicy.from_managed_policy_arn( scope=self, id=f"AmazonECSTaskExecutionRolePolicy_{stack_name}-{stack_env}", managed_policy_arn="arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" ) ) job_role.add_managed_policy( aws_iam.ManagedPolicy.from_managed_policy_arn( scope=self, id=f"AmazonS3FullAccess_{stack_name}-{stack_env}", managed_policy_arn="arn:aws:iam::aws:policy/AmazonS3FullAccess" ) ) job_role.add_managed_policy( aws_iam.ManagedPolicy.from_managed_policy_arn( scope=self, id=f"CloudWatchLogsFullAccess_{stack_name}-{stack_env}", managed_policy_arn="arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" ) ) batch_job_queue = aws_batch.JobQueue( scope=self, id=f"job_queue_for_{stack_name}-{stack_env}", job_queue_name=f"job_queue_for_{stack_name}-{stack_env}", compute_environments=[ aws_batch.JobQueueComputeEnvironment( compute_environment=batch_compute_environment, order=1 ) ], priority=1 ) # ECRリポジトリの取得 ecr_repository = aws_ecr.Repository.from_repository_arn( scope=self, id=f"image_for_{stack_name}-{stack_env}", repository_arn=self.ECR_REPOSITORY_ARN ) # ECRからイメージの取得 container_image = aws_ecs.ContainerImage.from_ecr_repository( repository=ecr_repository ) # ジョブ定義 # ここで、Python scriptで利用する`S3_BUCKET`を環境変数として渡す batch_job_definition = aws_batch.JobDefinition( scope=self, id=f"job_definition_for_{stack_env}", job_definition_name=f"job_definition_for_{stack_env}", container=aws_batch.JobDefinitionContainer( image=container_image, environment={ "S3_BUCKET": f"{YOUR_S3_BUCKET}" }, job_role=job_role, vcpus=1, memory_limit_mib=1024 ) )2.4.5 app.pyの実装(StepFunctions + CloudWatch Eventsの作成)
ここからは、必ずしもBatchの環境構築に必要ではありませんが、
定期実行をするためにStepFunctionsとCloudWatch Eventを利用して行います。CloudWatch Eventからも直接Batchを呼べますが、
他サービスとの連携のしやすさやパラメータの受け渡しなどを考えて間にStepFunctionsを挟んでいます。StepFunctionsのステップとして登録する際に、
DockerのCMDコマンドを(=Batchのジョブ定義に設定した状態)上書きして、
CloudWatch Eventからの引数time
を受け取り、Python scriptへ渡しています。app.py# Batchの続き... # ============= # # StepFunctions # # ============= # command_overrides = [ "python", "__init__.py", "--time", "Ref::time" ] batch_task = aws_sfn_tasks.BatchSubmitJob( scope=self, id=f"batch_job_{stack_env}", job_definition=batch_job_definition, job_name=f"batch_job_{stack_env}_today", job_queue=batch_job_queue, container_overrides=aws_sfn_tasks.BatchContainerOverrides( command=command_overrides ), payload=aws_sfn.TaskInput.from_object( { "time.$": "$.time" } ) ) # 今回は1ステップしかないので、単純ですが複数のステップをつなげたい場合は # batch_task.next(aws_sfn_tasks.JOB).next(aws_sfn_tasks.JOB) # のようにチェインメソッドで渡せます。 definition = batch_task sfn_daily_process = aws_sfn.StateMachine( scope=self, id=f"YourProjectSFn-{stack_env}", definition=definition ) # ================ # # CloudWatch Event # # ================ # # Run every day at 21:30 JST # See https://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html events_daily_process = aws_events.Rule( scope=self, id=f"DailySFnProcess-{stack_env}", schedule=aws_events.Schedule.cron( minute=31, hour=12, month='*', day="*", year='*'), ) events_daily_process.add_target(aws_events_targets.SfnStateMachine(sfn_daily_process)) # ここまで def __init__(...):2.4.6 app.pyの実装(main関数の実装)
最後に、CDKを実行する処理を書いたら、完了です。
app.py# ここに def __init__(...): def main(): app = core.App() BatchEnvironment(app, "your-project", "feature") BatchEnvironment(app, "your-project", "dev") BatchEnvironment(app, "your-project", "prod") app.synth() if __name__ == "__main__": main()2.5 デプロイ
上記のスクリプトが完成後に、以下のコマンドで正しくCDKの設定ができているか確認の上、デプロイしましょう。
Batchの環境を0から作成する場合でも10分程度で完了します。# 定義の確認 $ cdk synth Successfully synthesized to {path_your_project}/cdk.out Supply a stack id (your-project-dev, your-project-feature, your-project-prod) to display its template. # デプロイできる環境の確認 $ cdk ls your-project-dev your-project-feature your-project-prod $ cdk deploy your-project-feature ...deploying...2.5.1 環境が正しく作られたか確認する
デプロイが完了したら、コンソールから作成したStepFunctionsを選択し、「実行の開始」ボタンを押します。
time
の引数だけ入れてあげて、{ "time": "2020-09-27T12:31:00Z" }正しく動いたら完了です。
また、CloudWatch Logsへも想定した通りに動いているか確認しましょう。3. おわりに
CDKは、環境の構築と削除がコマンドですぐにできるのでめっちゃ好きです!
あと、コンソールから作成するよりも、プログラムのパラメータで求められているものがわかるので、
知らないサービスでも、どんなパラメータが必須かがわかりやすくて良いなと思いました!(いつか、GitHubのリポジトリにて上記のソースをまとめたものを展開します・・・!)