会社で AWS の勉強機会を頂けたので LocalStack(community) を使用した AWS のローカル開発環境構築をした時の手順や詰まった事を記載します。
今回コンテナやAWS、WSLに初めて触れたのでとても勉強になりました。
- 最終的に目指した構成
- 制約事項
- WSLのセットアップ
- DNSサーバの設定
- ルート証明書の設定
- Python関連のインストール
- AWSCLIのインストール
- dockerのインストール
- docker-composeのインストール
- LocalStackの起動
- MySQLサーバの起動
- Lambda関数の作成_1
- Lambda関数の作成_2
制約事項や独自の設定など
- ZScaler が入っているため、DSN サーバの設定やルート証明書の設定を随所で行っています。
これらの設定は通常であれば不要かもしれません。 - LocalStack(community) の制約のため、以下は独自の設定をしています。
- RDS は PRO のみの機能なので MySQL コンテナを実行して代用しています。
- サブスクリプションフィルタの --filter-pattern はPRO のみの機能なので空文字(全てを対象)としています。
WSLのセットアップ
- WSL のインストール・初期設定を行います。
Ubuntu 20.04 をインストールします。
# ディストリビューション一覧を表示
wsl --list --online
# 今回は Ubuntu-20.04 をインストール
# 途中ユーザ名やパスワードを入力します
wsl --install --distribution Ubuntu-20.04
root パスワードを設定しておきます。
# root ユーザのパスワード変更
sudo passwd root
# 変更したパスワードを使って root ユーザでログイン出来る事を確認
su
# ログイン出来たら root からログアウトする。
logout
- root パスワードが分からなくなった場合、powershell から root ユーザで入って変更する事が出来ます。
wsl --user root
DNSサーバの設定
- ZScaler が入っている場合、名前解決出来ないようなので DNS 設定を行いました。
不要であればスキップで問題ありません。
resolv.conf が自動生成(初期化)されないようにします。
sudo nano /etc/wsl.conf
/etc/wsl.conf
[network]
generateResolvConf = false
名前解決出来ないため DNS サーバを設定します。
# DNS サーバの設定を書き換えます。
sudo nano /etc/resolv.conf
/etc/resolv.conf
nameserver 8.8.8.8
ルート証明書の設定
- ZScaler などを入れている場合 SSL 通信に失敗するため、ZScaler のルート証明書を WSL に適用する必要があります。
不要であればスキップで問題ありません。
エクスポート手順
- win+R から「certlm.msc」を開く
- 「信頼されたルート証明機関」→「証明書」→「Zscaler Root CA」をダブルクリック
- 「詳細」→「ファイルにコピー」を選択
- 「次へ」
- 「Base 64 encoded X.509(.CER)(S)」→「次へ」
- 任意の保存先・ファイル名を入力して「次へ」
- 「完了」
インポート手順
- 証明書を WSL へコピーする。(/mnt/c/ は Windows 側の C ドライブになります。)
# .crt に変更して保存する。 sudo cp -p /mnt/c/zscaler.cer /usr/local/share/ca-certificates/zscaler.crt
- 証明書を読み込む
# 証明書を更新する。 sudo update-ca-certificates --fresh # 試しに SSL 通信を実行する。 curl -O https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh
Python関連のインストール
- pyenv, Python, poetry をインストールします。
apt のソースリストを変更します。
※私は頻繁にタイムアウトになってしまったので更新しましたが、不要かもしれません。
# バックアップ
sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup
# 中身を更新
sudo nano /etc/apt/sources.list
/etc/apt/sources.list
deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse
deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse
deb-src http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
deb-src http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse
deb-src http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse
deb http://archive.canonical.com/ubuntu focal partner
deb-src http://archive.canonical.com/ubuntu focal partner
パッケージ一覧を更新しておきます。
sudo apt update
sudo apt upgrade
pyenv をインストールします。
curl https://pyenv.run | bash
メッセージ通りに「~/.bashrc」に追記します。
sudo nano ~/.bashrc
source ~/.bashrc
# pyenv のバージョンを確認します。
pyenv --version
~/.bashrc
(省略)
export PYENV_ROOT="$HOME/.pyenv"
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
pyenv が Python 導入時のビルドで使用するパッケージをインストールしておきます。
Home · pyenv/pyenv Wiki · GitHub
sudo apt update; sudo apt install build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev curl libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
pyenv を使って Python をインストールします。
# インストール可能な Python の一覧を出力します。
pyenv install --list | grep "3\.8\."
# 今回は 3.8.16 をダウンロードします。
pyenv install 3.8.16
# システムで使用する Python バージョンを設定します。
pyenv global 3.8.16
# Python のバージョンを確認します。
pyenv version
# Python が利用する SSL 証明書を設定します。
# (上で配備した zscaler.crt を指定。通常の環境では不要)
pip config set global.cert /usr/local/share/ca-certificates/zscaler.crt
poetry をインストールします。
curl -sSL https://install.python-poetry.org | python -
# powershell から一度シャットダウンします。
wsl --shutdown
# シャッドダウン後 WSL でバージョンを確認します。
poetry --version
AWSCLIのインストール
- CLI で AWS を操作するためのコマンドをインストールします。
aws cli をインストールします。
sudo apt install awscli
asc cli local をインストールします。
- localstack に向けて aws コマンドを実行する際に利用します。
pip install awscli-local==0.20
dockerのインストール
- 手順に従ってインストールします。
Running Docker on WSL2 without Docker Desktop (the right way)
下記に公式の手順もありますが、上記の手順はサービス立ち上げの自動化なども載っているのでそちらでインストールしました。
Install Docker Engine on Ubuntu | Docker Documentation
docker-composeのインストール
- 手順に従ってインストールします。
How To Install and Use Docker Compose on Ubuntu 20.04
LocalStackの起動
- AWS をローカルで再現するために LocalStack を起動します。
mkdir localstack
cd localstack
# 設定ファイルを編集します。
nano docker-compose.yml
# コンテナを起動します。
# docker-compose up はログ確認に使います。
# ログ確認が不要な場合は docker-compose up -d で起動するとバックグラウンドで実行されます。
docker-compose up
# 別ターミナルで起動している事を確認します。
curl http://localhost:4566/health
docker-compose.yml
version: "3.8"
services:
localstack:
image: localstack/localstack:2.0.1
ports:
- "127.0.0.1:4566:4566" # LocalStack Gateway
environment:
- DEBUG=1
- DOCKER_HOST=unix:///var/run/docker.sock
# マウント先のSSL証明書を設定します。
- REQUESTS_CA_BUNDLE=/usr/local/share/ca-certificates/zscaler.crt
- CURL_CA_BUNDLE=/usr/local/share/ca-certificates/zscaler.crt
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
# LocalStackコンテナから SSL 通信をする際にSSL証明書が必要になるのでマウントします。
- "/usr/local/share/ca-certificates/zscaler.crt:/usr/local/share/ca-certificates/zscaler.crt"
networks:
aws-network:
aliases:
# エイリアスを設定しておきます。
- localstack
# コンテナを所属させるネットワークを設定します。
networks:
aws-network:
name: aws-network
driver: bridge
MySQLサーバの起動
- RDS は LocalStackPro のみの機能なので、別コンテナで DB を立ち上げます。
mkdir mysql
cd mysql
# 設定ファイルを編集します。
nano docker-compose.yml
# コンテナを起動します。
docker-compose up -d
# MySQL client をインストールします。
sudo apt install mysql-client
# MySQLに接続出来る事を確認します。
mysql --user mysql --port 3306 --host 0.0.0.0 --password
docker-compose.yml
version: "3.8"
services:
mysql:
platform: linux/x86_64
image: mysql:latest
restart: always
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: mysql_db
MYSQL_USER: mysql
MYSQL_PASSWORD: mysql
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
TZ: Asia/Tokyo
ports:
- 3306:3306
volumes:
- ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
- ./db:/var/lib/mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
# LocalStackと同じネットワークに設定しておく。
networks:
- aws-network
networks:
aws-network:
name: aws-network
driver: bridge
Lambda関数の作成_1
- S3, MySQL との接続確認を行う Lambda 関数を作成します。
Python プロジェクトを作成します。
mkdir lambda_test
cd lambda_test
poetry init
Lambda で実行する Python ファイルを作成します。
mkdir lambda_test
nano lambda_test/lambda_test.py
lambda_test.py
import mysql.connector
import json
import boto3
from boto3.session import Session
from datetime import datetime
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
DB_USER = "mysql"
DB_PASS = "mysql"
DB_NAME = "mysql_db"
DB_HOST = "mysql"
DB_PORT = "3306"
DB_TABLE = ""
session = Session(aws_access_key_id='dummy',
aws_secret_access_key='dummy',
region_name='ap-northeast-1'
)
# endpoint_url は docker-compose.yml で指定したエイリアスを使っています。
s3 = session.resource(service_name='s3', endpoint_url='http://localstack:4566')
def lambda_handler(event, context):
conn = None
results = None
logger.info(event)
try:
# MySQL の接続テスト
conn = mysql.connector.connect(
user=DB_USER,
password=DB_PASS,
database=DB_NAME,
host=DB_HOST,
port=DB_PORT
)
cur = conn.cursor()
cur.execute(f"SELECT NOW();")
results = cur.fetchall()
for result in results:
date_time = f"{result[0]}"
logger.info(date_time)
# S3 の接続テスト
for bucket in s3.buckets.all():
logger.info(bucket.name)
except Exception as e:
logger.info(f"Error Occurred: {e}")
finally:
if conn is not None and conn.is_connected():
conn.close()
return {
"statusCode": 200,
"body": json.dumps({
"res": date_time,
"event": event
}),
}
poetry でパッケージを追加します。
# poetry で新しくプロジェクトを作成すると SSL 通信で利用する証明書の再設定が必要なので、プロジェクトを作成したら下記を実行します。(恐らく ZScaler 使用の場合のみ)
poetry source add --default pypiorg https://pypi.org/simple/
poetry source add files https://files.pythonhosted.org/
poetry config certificates.pypiorg.cert /usr/local/share/ca-certificates/zscaler.crt
poetry config certificates.files.cert /usr/local/share/ca-certificates/zscaler.crt
# パッケージを追加します。
poetry add mysql-connector-python boto3
ビルドを行うbuild.sh を作成・実行します。
- 必要なパッケージと作成したlambda_test.pyをzipにまとめます。
nano build.sh
sudo apt install zip unzip
sh build.sh
build.sh
poetry export -f requirements.txt --output requirements.txt;
pip install -r requirements.txt --target ./packages;
cd packages/;
zip -r ../package.zip .;
cd ..;
cp lambda_test/lambda_test.py lambda_test.py; zip -g package.zip lambda_test.py; rm -rf lambda_test.py;
S3の作成・Lambda 関数をアップロードする aws_create.sh を作成・実行します。
nano aws_create.sh
# 実行前に LocalStack を実行しておく。
sh aws_create.sh
aws_create.sh
awslocal s3 mb s3://my-bucket
awslocal lambda create-function \
--function-name test_s3_mysql_lambda_function \
--timeout 3 \
--runtime python3.8 \
--zip-file fileb://package.zip \
--handle lambda_test.lambda_handler \
--role arn:aws:iam::000000000000:role/lambda-role
Lambda 関数を起動する aws_invoke.sh を作成・実行します。
nano aws_invoke.sh
sh aws_invoke.sh
aws_invoke.sh
awslocal lambda invoke --function-name test_s3_mysql_lambda_function result.log
LocalStack のログからMySQLへの接続、S3への接続が出来ている事が確認出来ました。
logs を確認すると CloudWatchLogs にログが書き込まれている事も確認出来ます。
awslocal logs describe-log-streams --log-group-name /aws/lambda/test_s3_mysql_lambda_function --query "logStreams[].[log
StreamName]" --output text
# --log-stream-name は上記で取得したもの
awslocal logs get-log-events --log-group-name /aws/lambda/test_s3_mysql_lambda_function --log-stream-name '2023/04/27/[$LATEST]cbc1850e7c010e24a4a6740c64fb654c' --query "events[].[message]" --output json | jq
Lambda関数の作成_2
- S3, MySQL の接続に加えて CloudWatch のサブスクリプションフィルタで別の Lambda を起動する処理を追加したシステムを構築します。
- 今回は Lambda 関数が2つ必要なため、プロジェクトを2つ作成します。
Python プロジェクトを作成します。(1つ目)
- まずは MySQL に接続する Lambda 関数を作成します。
mkdir lambda_get_timestamp
cd lambda_get_timestamp
poetry init
Lambda で実行する Python ファイルを作成します。
mkdir lambda_get_timestamp
nano lambda_get_timestamp/lambda_get_timestamp.py
lambda_get_timestamp.py
import mysql.connector
import json
from datetime import datetime
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
DB_HOST = "mysql"
DB_PORT = "3306"
DB_USER = "mysql"
DB_NAME = "mysql_db"
DB_PASS = "mysql"
DB_TABLE = ""
def lambda_handler(event, context):
conn = None
results = None
try:
conn = mysql.connector.connect(
user=DB_USER,
password=DB_PASS,
database=DB_NAME,
host=DB_HOST,
port=DB_PORT
)
if conn.is_connected:
logger.info("Connected!")
cur = conn.cursor()
cur.execute(f"SELECT NOW();")
results = cur.fetchall()
for result in results:
date_time = f"{result[0]}"
logger.info("GETDATETIME:" + date_time)
except Exception as e:
logger.info(f"Error Occurred: {e}")
finally:
if conn is not None and conn.is_connected():
conn.close()
return {
"statusCode": 200,
"body": json.dumps({
"res": date_time,
"event": event
}),
}
poetry でパッケージを追加します。
# poetry で新しくプロジェクトを作成すると SSL 通信で利用する証明書の再設定が必要なので、プロジェクトを作成したら下記を実行します。(恐らく ZScaler 使用の場合のみ)
poetry source add --default pypiorg https://pypi.org/simple/
poetry source add files https://files.pythonhosted.org/
poetry config certificates.pypiorg.cert /usr/local/share/ca-certificates/zscaler.crt
poetry config certificates.files.cert /usr/local/share/ca-certificates/zscaler.crt
# パッケージを追加します。
poetry add mysql-connector-python
ビルドを行うbuild.sh を作成・実行します。
- 必要なパッケージと作成した lambda_get_timestamp.py をzipにまとめます。
nano build.sh
# 先に入れてあれば不要
sudo apt install zip unzip
sh build.sh
build.sh
poetry export -f requirements.txt --output requirements.txt;
pip install -r requirements.txt --target ./packages;
cd packages/;
zip -r ../package.zip .;
cd ..;
cp lambda_get_timestamp/lambda_get_timestamp.py lambda_get_timestamp.py; zip -g package.zip lambda_get_timestamp.py; rm -rf lambda_get_timestamp.py;
Lambda 関数をアップロードする aws_create.sh を作成します。
- アップロードは後ほど行うので、ここでは aws_create.sh の作成のみです。
nano aws_create.sh
aws_create.sh
awslocal lambda create-function \
--function-name get_timestamp_lambda_function \
--timeout 3 \
--runtime python3.8 \
--zip-file fileb://package.zip \
--handle lambda_get_timestamp.lambda_handler \
--role arn:aws:iam::000000000000:role/lambda-role
awslocal logs create-log-group \
--log-group-name /aws/lambda/get_timestamp_lambda_function
# destination-arn に書かれている Lambda 関数を呼び出します。
# --filter-pattern は PRO のみの機能のようで、フィルタ条件を書いても無視されてしまいました。
# 今回は /aws/lambda/get_timestamp_lambda_function にイベントが書かれた時点で put_s3_lambda_function が起動します。
awslocal logs put-subscription-filter \
--log-group-name "/aws/lambda/get_timestamp_lambda_function" \
--filter-name get_timestamp_lambda_filter \
--filter-pattern "" \
--destination-arn arn:aws:lambda:us-east-1:000000000000:function:put_s3_lambda_function
Lambda 関数を起動する aws_invoke.sh を作成します。
nano aws_invoke.sh
aws_invoke.sh
awslocal lambda invoke --function-name get_timestamp_lambda_function result.log
Python プロジェクトを作成します。(2つ目)
- S3 にファイルを PUT する Lambda 関数を作成します。
- サブスクリプションフィルタで呼び出されます。
mkdir lambda_put_s3
cd lambda_put_s3
poetry init
Lambda で実行する Python ファイルを作成します。
mkdir lambda_put_s3
nano lambda_put_s3/lambda_put_s3.py
lambda_put_s3.py
import json
import base64
import gzip
import re
import boto3
from boto3.session import Session
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
session = Session(aws_access_key_id='dummy',
aws_secret_access_key='dummy',
region_name='ap-northeast-1'
)
s3 = session.resource(service_name='s3', endpoint_url='http://localstack:4566')
def lambda_handler(event, context):
try:
# base64 デコード
decoded_data = base64.b64decode(event["awslogs"]["data"])
# gzip 解凍
decompressed_data = gzip.decompress(decoded_data)
json_data = json.loads(decompressed_data)
logger.info(json_data)
# DATETIME: が書かれているログの一部を抜き出します。
for logEvent in json_data["logEvents"]:
log = re.search(r"GETDATETIME:.*", logEvent["message"])
if log != None:
body = log.group()
logger.info(body)
break
bucket_name = "my-bucket"
key = "timestamp.txt"
s3.Bucket(bucket_name).put_object(Key=key, Body=body)
except Exception as e:
logger.info(f"Error Occurred: {e}")
return {
"statusCode": 200,
"body": json.dumps({
"res": "S3TEST",
"event": event
}),
}
poetry でパッケージを追加します。
# poetry で新しくプロジェクトを作成すると SSL 通信で利用する証明書の再設定が必要なので、プロジェクトを作成したら下記を実行します。(恐らく ZScaler 使用の場合のみ)
poetry source add --default pypiorg https://pypi.org/simple/
poetry source add files https://files.pythonhosted.org/
poetry config certificates.pypiorg.cert /usr/local/share/ca-certificates/zscaler.crt
poetry config certificates.files.cert /usr/local/share/ca-certificates/zscaler.crt
# パッケージを追加します。
poetry add boto3
ビルドを行うbuild.sh を作成・実行します。
- 必要なパッケージと作成した lambda_put_s3.py をzipにまとめます。
nano build.sh
# 先に入れてあれば不要
sudo apt install zip unzip
sh build.sh
build.sh
poetry export -f requirements.txt --output requirements.txt;
pip install -r requirements.txt --target ./packages;
cd packages/;
zip -r ../package.zip .;
cd ..;
cp lambda_put_s3/lambda_put_s3.py lambda_put_s3.py; zip -g package.zip lambda_put_s3.py; rm -rf lambda_put_s3.py;
Lambda 関数をアップロードする aws_create.sh を作成します。
nano aws_create.sh
aws_create.sh
awslocal s3 mb s3://my-bucket
awslocal lambda create-function \
--function-name put_s3_lambda_function \
--timeout 10 \
--runtime python3.8 \
--zip-file fileb://package.zip \
--handle lambda_put_s3.lambda_handler \
--role arn:aws:iam::000000000000:role/lambda-role
Lambda 関数の作成と実行と確認
# 関数のアップロードと起動
cd ~/lambda_put_s3
sh aws_create.sh
cd ~/lambda_get_timestamp
sh aws_create.sh
sh aws_invoke.sh
# 関数の実行完了後
awslocal s3 ls s3://my-bucket
awslocal s3 cp s3://my-bucket/timestamp.txt ./
cat timestamp.txt
- get_timestamp_lambda_function を起動した後、put_s3_lambda_function がサブスクリプションフィルタによって起動して S3 にファイルが作成されている事を確認出来ました。
- 本番環境ではサブスクリプションフィルタ作成時、Lambda 関数にパーミッション追加などの設定が必要らしいので、また試していきたいと思います。