4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

LocalStack による AWS 開発環境の構築(WSL2、ZScaler、Lambda、S3、CloudWatch、サブスクリプションフィルタ)

Last updated at Posted at 2023-04-27

会社で AWS の勉強機会を頂けたので LocalStack(community) を使用した AWS のローカル開発環境構築をした時の手順や詰まった事を記載します。
今回コンテナやAWS、WSLに初めて触れたのでとても勉強になりました。


制約事項や独自の設定など

  • 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

image.png

root パスワードを設定しておきます。

# root ユーザのパスワード変更
sudo passwd root
# 変更したパスワードを使って root ユーザでログイン出来る事を確認
su
# ログイン出来たら root からログアウトする。
logout

image.png

  • root パスワードが分からなくなった場合、powershell から root ユーザで入って変更する事が出来ます。
wsl --user root

image.png

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

image.png

ルート証明書の設定

  • ZScaler などを入れている場合 SSL 通信に失敗するため、ZScaler のルート証明書を WSL に適用する必要があります。
    不要であればスキップで問題ありません。

エクスポート手順

  1. win+R から「certlm.msc」を開く
    image.png
  2. 「信頼されたルート証明機関」→「証明書」→「Zscaler Root CA」をダブルクリック
    image.png
  3. 「詳細」→「ファイルにコピー」を選択
    image.png
  4. 「次へ」
    image.png
  5. 「Base 64 encoded X.509(.CER)(S)」→「次へ」
    image.png
  6. 任意の保存先・ファイル名を入力して「次へ」
    image.png
  7. 「完了」
    image.png

インポート手順

  1. 証明書を WSL へコピーする。(/mnt/c/ は Windows 側の C ドライブになります。)
    # .crt に変更して保存する。
    sudo cp -p /mnt/c/zscaler.cer /usr/local/share/ca-certificates/zscaler.crt
    
  2. 証明書を読み込む
    # 証明書を更新する。
    sudo update-ca-certificates --fresh
    # 試しに SSL 通信を実行する。
    curl -O https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh
    

image.png

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」に追記します。

image.png

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

image.png

poetry をインストールします。

curl -sSL https://install.python-poetry.org | python -
# powershell から一度シャットダウンします。
wsl --shutdown
# シャッドダウン後 WSL でバージョンを確認します。
poetry --version

image.png

AWSCLIのインストール

  • CLI で AWS を操作するためのコマンドをインストールします。

aws cli をインストールします。

sudo apt install awscli

asc cli local をインストールします。

  • localstack に向けて aws コマンドを実行する際に利用します。
pip install awscli-local==0.20

image.png

dockerのインストール

docker-composeのインストール

  • 手順に従ってインストールします。
    How To Install and Use Docker Compose on Ubuntu 20.04
    • インストールする際のバージョンは git から最新版のURLを取得しました。
      sudo curl -L "https://github.com/docker/compose/releases/download/v2.17.3/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
      
      image.png
      image.png

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

image.png

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

image.png

Lambda関数の作成_1

  • S3, MySQL との接続確認を行う Lambda 関数を作成します。

Python プロジェクトを作成します。

mkdir lambda_test
cd lambda_test
poetry init

image.png

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

image.png

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への接続が出来ている事が確認出来ました。
image.png

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

image.png

Lambda関数の作成_2

  • S3, MySQL の接続に加えて CloudWatch のサブスクリプションフィルタで別の Lambda を起動する処理を追加したシステムを構築します。
  • 今回は Lambda 関数が2つ必要なため、プロジェクトを2つ作成します。

Python プロジェクトを作成します。(1つ目)

  • まずは MySQL に接続する Lambda 関数を作成します。
mkdir lambda_get_timestamp
cd lambda_get_timestamp
poetry init

image.png

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

image.png

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 にファイルが作成されている事を確認出来ました。
    image.png

  • 本番環境ではサブスクリプションフィルタ作成時、Lambda 関数にパーミッション追加などの設定が必要らしいので、また試していきたいと思います。
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?