20200209のAWSに関する記事は19件です。

SQSキュー作成時のパラメータ

はじめに

今日はAWS SQS(以降SQS)について調べました。
こういうサービスのことは何となく大まかにはわかっていても、知っていること同士がうまく紐付かなかったリ必要な時に出てこなかったりするので、とにかく実機で手を動かしながら各項目について調べまくるようにしようと思っています。

(最近仕事でVPCピアリングを設定する機会がありましたが、すごく基本的なことがわかっていないなと思ったりするなど)

SQSキュー作成

今日の参考手順。
https://docs.aws.amazon.com/ja_jp/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-create-queue.html

手順はマネジメントコンソールでポチポチやるだけなので、設定する項目についてまとめます。

必須パラメータ

設定値1 : キューのタイプ

コンソールの表示順としては前後しますが、キューの名前を決める前にSQSタイプを指定します。
ざっくり言うと費用面で標準キュー、性能面でFIFOキューが勝りますが、それぞれ用途が異なります。

標準キュー

  • 無制限のスループット
  • 配信順序は保証されない(ベストエフォート型)
  • メッセージは最低1回配信する(複数回配信されることもある)

スループットが高く(FIFOキューに比べて)安価であるため、配信順序を気にする必要がないバッチ処理などに向いています。

FIFOキュー

  • スループットは1秒あたり300件のメッセージ処理をサポート(AWSにリクエストを上げれば最大3000件)
  • 配信順序は厳密に保証
  • メッセージは必ず1回のみ配信

標準キューがベストエフォートとしている部分が全て解決されているため、オンラインショップやストリーミングなどの入力順や処理回数が重要な処理に向いています。

設定値2 : キュー名

キューの名前は基本的に自由ですが、FIFOキューを選択した場合は末尾に「.fifo」を付ける必要があります。
(逆に、標準キューの場合は「.fifo」を付けてはいけない)

キュー属性

ここからはオプションです。

※「」はマネジメントコンソール上での説明文

設定値1 : 可視性タイムアウト

「キューから受信したメッセージが他の受信コンポーネントから見えない時間の長さ(秒)です。」

これを読んで正直何のこっちゃ、と思ったので少し突っ込んで調べました。

どうもSQSではメッセージを配信した後もキューから自動的には削除されず、そのまま残り続ける仕様となっているようです。
(非同期という特性上、相手が正しく受信できたかを確認できないため)
そのため、メッセージを受信して処理が終わった後明示的にメッセージの削除を行う必要があります。

この「受信~処理~削除」を行っている間にキューに残っているメッセージを他のコンシューマー(受信者)が処理されてしまうのを避けるのが可視性タイムアウトであり、これを設定することで指定した時間メッセージを受信・処理されないようになります。
可視性タイムアウトが過ぎれば、当然他のコンシューマーでも受信が可能になります(一回のみの配信が保証されているFIFOキューの場合は除く)

デフォルト値は30秒で、0秒~12時間の中で設定可能です。

Amazon SQS 可視性タイムアウト

チュートリアル: Amazon SQS キューからのメッセージの受信および削除

設定値2 : メッセージ保持期間

「メッセージが削除されない場合に Amazon SQS で保持される時間です。」

キューにある古いメッセージを削除するハウスキーピング的な処理の期限を決める設定値。

デフォルトは4日で、1分~14日の中で設定可能です。

基本的な Amazon SQS アーキテクチャ

設定値3 : 最大メッセージサイズ

「Amazon SQS が受け付ける最大メッセージサイズ(バイト)です。」

SQSが受付可能なメッセージの最大サイズを指定する設定値。

デフォルトは256KBで、1B~256KBの中で設定可能です。

設定値4 : 配信遅延

「このキューに追加されたすべてのメッセージの初回配信の遅延時間です。」

新しいメッセージをコンシューマーに配信するのを指定した時間待たせる(遅延させる)ことができるもの。

(コンシューマー側のアプリケーションがメッセージの処理に追加の時間を必要とする場合に使える、とのことらしいです。自分は開発者ではないのであまりピンと来ていませんが)

コンシューマーでメッセージが受信できなくなる、という点で機能的には可視性タイムアウトに似ていて、こちらはキューに入った直後に発動するものになります。

デフォルトは0秒(遅延無し)で、0秒~15分の中で設定可能です。

Amazon SQS 遅延キュー

設定値5 : メッセージ受信待機時間

「ロングポーリング受信呼び出しが空の応答を返すまでに、メッセージが利用可能になるまで待機する最大時間です。」

平たく言うと、コンシューマーがキューにメッセージを取得しに行った際、受信可能なメッセージが無い場合に待機する時間です。
待機時間を設定しない場合をショートポーリング、設定する場合をロングポーリングと説明されます。

SQSでは受信可能なメッセージが無い場合に空のレスポンスを返します。
(アプリケーション側で空のレスポンスを処理できれば)これでも問題ないのですが、SQSはリクエスト課金のため、空のレスポンスを受け取る回数が多いと料金が上がってしまうため、費用削減に役立ちます。

ちなみにロングポーリンとショートポーリングの違いはAWS認定資格(SOAやDVAなど)でも出題されます。

デフォルトは0秒で、0秒~20秒の中で設定可能です。
ちなみにロングポーリングを設定していても必ずその時間待機するというわけではなく、新しいメッセージが来たらその時点でコンシューマーにレスポンスを戻します(あくまでメッセージが来るまで待つ時間のようです)

Amazon SQS ショートポーリングとロングポーリング

設定値6 : コンテンツに基づく重複排除(※FIFOキュー限定)

「メッセージの本文 (メッセージの属性ではない) のSHA-256 ハッシュを使用してコンテンツベースのメッセージ重複排除 ID を生成します。」

FIFOキューの場合、メッセージの配信は1回のみであるため、同じメッセージの配信リクエストがあった場合は削除される仕様になっています。
その重複メッセージかどうかの判定を行っているのがこのメッセージ重複排除IDで、それを自動で生成するかどうかの設定値です。

Amazon SQS メッセージ重複排除 ID の使用

【新機能】Amazon SQSにFIFOが追加されました!(重複削除/単一実行/順序取得に対応)

Amazon SQS メッセージグループ ID の使用

デッドレターキュー設定

デッドレターキューとは、正常に処理できないメッセージを格納するキューです。

Amazon SQS デッドレターキュー

サーバー側の暗号化 (SSE) の設定

メッセージの暗号化に使う暗号キーを指定する項目で、デフォルトではAWS KMSで管理されているキーを使いますが、顧客側の暗号キー(カスタマーキー)を使うことも可能です。

CLIコマンド

CLIで作成する場合は下記になります。

### 標準キュー ###
> aws sqs create-queue --queue-name make-test-queue

### FIFOキュー ###
> aws sqs create-queue --queue-name make-test-queue.fifo --attributes "FifoQueue"=True

### 作成確認 ###
> aws sqs list-queues --output table
------------------------------------------------------------------------------------
|                                    ListQueues                                    |
+----------------------------------------------------------------------------------+
||                                    QueueUrls                                   ||
|+--------------------------------------------------------------------------------+|
||  https://ap-northeast-1.queue.amazonaws.com/XXXXXXXXXXXX/make-test-queue       ||
||  https://ap-northeast-1.queue.amazonaws.com/XXXXXXXXXXXX/make-test-queue.fifo  ||
|+--------------------------------------------------------------------------------+|

※参考URL
https://docs.aws.amazon.com/cli/latest/reference/sqs/create-queue.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TABLE 形式の Aurora(MySQL 互換)スロークエリログを Elasticsearch Service (6.8) に取り込み S3 に保存するメモ(Lambda Python 3.8 版)

こちらは、

の Elasticsearch Service 6.8 / Lambda Python 3.8 対応化メモです。

1 ヶ月ほど前に、

を掲載しましたが、1 つだけ忘れたままになっていましたので追加で掲載します。

※いまは Aurora MySQL 互換版から CloudWatch Logs へほぼリアルタイムにスロークエリログを書き出すことができるので、この記事の方法よりもそちらを使うことをお勧めします。

手順

1. Amazon Elasticsearch Service の起動

ALB の元記事のとおりです(省略)。

2. Ingest Pipeline の設定

元記事と同じですが再掲します(curlコマンドで設定)。

curlでPipeline設定
$ curl -H "Content-Type: application/json" -XPUT 'https://XXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXX.ap-northeast-1.es.amazonaws.com/_ingest/pipeline/slowlog' -d '{
"processors": [{
    "grok": {
      "field": "message",
      "patterns":[ "%{TIMESTAMP_ISO8601:timestamp} %{NOTSPACE:user_host} %{INT:query_time:int} %{INT:lock_time:int} %{INT:rows_sent:int} %{INT:rows_examined:int} %{NOTSPACE:db} %{GREEDYDATA:sql_test}" ],
      "ignore_missing": true
    }
  },{
    "remove":{
      "field": "message"
    }
  }]
}'

3. AWS Lambda ファンクションの作成

IAM Role の作成

ここまでは元記事のとおりです(省略)。

Lambda にアップロードする .zip ファイルの作成(Amazon Linux 2 上で)

Lambda用アップロードファイル.zip化
$ python3 -m venv dev
$ . dev/bin/activate
(dev) $ mkdir aurora_log_to_es_s3
(dev) $ cd aurora_log_to_es_s3/
(dev) $ pip install requests pymysql requests_aws4auth -t ./
(省略)
(dev) $ rm -rf *.dist-info
(dev) $ vi auroralog.py
(ここで「auroralog.py」のコードを入力)
(dev) $ zip -r ../auroralog.zip
auroralog.py
import boto3
import datetime
import os
import pymysql
import re
import sys
import requests
from requests_aws4auth import AWS4Auth

aurora_host = os.environ["AURORA_HOST"]
aurora_user = os.environ["AURORA_USER"]
aurora_pass = os.environ["AURORA_PASS"]

try:
    conn = pymysql.connect(aurora_host, user=aurora_user, passwd=aurora_pass, db="mysql", connect_timeout=10)
except:
    print("ERROR: Could not connect to Aurora instance : [%s]." % aurora_host)
    sys.exit()

def lambda_handler(event, context):

    print("Started")
    lasthour = datetime.datetime.today() - datetime.timedelta(hours = (1 - 9))
    exportdate1 = lasthour.strftime('%Y%m%d')
    exportdate2 = lasthour.strftime('%Y-%m-%d')
    exporttime = lasthour.strftime('%H')
    es_host = os.environ["ES_HOST"]
    es_index = os.environ["ES_INDEX_PREFIX"] + "-" + exportdate1
    s3_bucket = os.environ["S3_BUCKET"]
    s3_key = os.environ["S3_PREFIX"] + "/" + exportdate2 + "_" + exporttime
    region = os.environ["AWS_REGION"]
    service = 'es'
    credentials = boto3.Session().get_credentials()
    awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)

    print("Export: " + exportdate1 + "_" + exporttime)

    with conn.cursor() as cur:
        cur.execute("SELECT CONCAT(DATE(CONVERT_TZ(start_time, '+09:00', 'UTC')), 'T', TIME(CONVERT_TZ(start_time, '+09:00', 'UTC')), 'Z ', REPLACE(user_host, ' ', ''), ' ', TIME_TO_SEC(query_time), ' ', TIME_TO_SEC(lock_time), ' ', rows_sent, ' ', rows_examined, ' ', (CASE WHEN db='' THEN '-' ELSE db END), ' ', sql_text) FROM mysql.slow_log WHERE start_time BETWEEN '%s %s:00:00' AND '%s %s:59:59.999'" % (exportdate2, exporttime, exportdate2, exporttime))
        file_data = ""
        file_count = 1
        es_data = ""

        for line in cur:
            line_data = re.sub(r"(\\t| )+", " ", re.sub(r"\?+", "?", "".join(str(line)).strip()[2:-3].replace('"', '\\"')))
            file_data += "%s\n" % line_data
            es_data += '{"index":{"_index":"%s","_type":"log"}}\n' % es_index
            es_data += '{"message":"%s"}\n' % line_data
            if len(file_data) > 3000000:
                s3_client = boto3.client("s3")
                s3_client.put_object(
                  Bucket=s3_bucket,
                  Key=s3_key + "-" + str(file_count),
                  Body=file_data
                )
                #print("--- file: %s" % file_count)
                #print(file_data)
                file_data = ""
                file_count += 1
            if len(es_data) > 3000000:
                _bulk(es_host, es_data, awsauth)
                #print("--- es")
                #print(es_data)
                es_data = ""

        if file_data != "":
            s3_client = boto3.client("s3")
            s3_client.put_object(
              Bucket=s3_bucket,
              Key=s3_key + "-" + str(file_count),
              Body=file_data
            )
            #print("--- file: %s" % file_count)
            #print(file_data)

        if es_data != "":
            _bulk(es_host, es_data, awsauth)
            #print("--- es")
            #print(es_data)

    return "Completed"

def _bulk(host, doc, awsauth):
    pipeline = os.environ["PIPELINE_NAME"]

    url = "https://%s/_bulk?pipeline=%s" % (host, pipeline)
    headers = {"Content-Type": "application/json"}
    response = request(url, awsauth, headers=headers, data=doc)

    if response.status_code != requests.codes.ok:
        print(response.text)

def request(url, awsauth, headers=None, data=None):
    return requests.post(url, auth=awsauth, headers=headers, data=data)

Lambda ファンクションの作成

ここから先は元記事と同じです(省略)。

注意点

注意点も基本的には元記事のとおりです。

ただし、Python 3 系で実行することになったため、ログに含まれる全角文字はそのまま記録されます。

機密情報・個人情報保護の関係でそのまま記録したくない場合は、Percona Toolkit に含まれる pt-fingerprint などを使って SQL をノーマライズすると良いでしょう。


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker を使って Djangoチュートリアルの Polls アプリを AWS ECS へデプロイするサンプルのようなもの

はじめに

こちらはDjangoのチュートリアル「はじめての Django アプリ作成、その1」のPolls(投票)アプリを Amazon Web Service(AWS) の Elastic Container Service(ECS) へデプロイするサンプル(のようなもの)です。

nginx + uwsgi + python + django + MySQL DB という構成で docker-compose を使っています。
また、デプロイには AWS CLI と ECS CLI を使い AWSコンソールは(ほとんど)使いません。(EC2インスタンス への ssh 接続でセキュリティグループを編集するときにだけコンソールを使用します)

前半はローカル環境上で docker-compose を使い Polls アプリを作成して動かすところまで。
後半で前半に作成した Polls アプリを ECS へデプロイします。

ソースは GitHub 上に公開しています。
(但しDjangoアプリの部分はこれから作るため含まれていません。)
https://github.com/Brokenumbrella/django-ecs-sample
この説明の中でも「1. 前準備」の中で上記から clone します。

ここでは説明しないこと

  • git や docker のインストール方法と使い方など。
  • AWS アカウントの取得方法や AWS の仕組みなど。
  • AWS CLI, ECS CLI のインストール方法や使い方など。

前提条件

  • git をインストールしている。
  • docker をインストールしている。
  • AWS のアカウントを持っている(フルアクセス出来るものが良いと思います)。
    もしAWSのアカウントが無くても前半のローカル環境で動かす所までは出来ます。
  • AWS CLI をインストールしている。
    インストール方法は後半でAWSサイトへのリンクを載せています。
    そこでインストールするのでも構いません。
    インストールしなくても前半のローカル環境で動かす所までは出来ます。
  • ECS CLI をインストールしている。
    インストール方法は後半でAWSサイトへのリンクを載せています。
    そこでインストールするのでも構いません。
    インストールしなくても前半のローカル環境で動かす所までは出来ます。
  • インターネットに接続できる。
  • 作るプロジェクトの名前は mysite です。
    (変更したい場合は Grep で mysite の文字列を全て変更してください)

Python は docker コンテナで構築するため事前にインストールしておく必要はありません。
と言いたい所ですが、AWS CLI が Python 2.7 もしくは 3.4 以降を使います。
AWS CLI をインストールする際は入っていなければ Python のインストールが必要です。

動作確認済みの環境

  • Ubuntu 18.04.3 LTS
    Docker version 19.03.5, build 633a0ea838
    docker-compose version 1.25.0, build 0a186604

  • Windows 10 Enterprise 1903 build 18362.418
    Docker version 19.03.5, build 633a0ea
    docker-compose version 1.24.1, build 4667896b
    (Docker for Windows を使用)

【注意】Windows ではコマンドの実行を Git-Bash 上で行って下さい。
PowerShell や コマンドプロンプト では id コマンドが使えず動きません。

構成

  • OS : Alpine3
  • 言語 : Python3(Python公式の DockerImage をベースにしています)
  • フレームワーク : Django2
  • WSGIサーバー : uwsgi
  • Webサーバ : nginx
  • データベース : mysql8

各インストールバージョン

  • Alpine 3.1.0
  • Python 3.7.4
  • Django 2.2.10 以上 3.0 未満
  • uwsgi 2.0.18 以上 3.0 未満
  • nginx 1.17.8
  • MySQL 8.0.19
  • mysqlclient 1.4.6 以上 2.0 未満

動作確認は上記の一番低い(確認時のリリース)バージョンで行っています。
Python と Alpine は Python の公式リポジトリにあればどれでも使う事が出来ると思います。
nginx の別バージョンを使いたい場合は ./docker-compose.yml と ./web/Dockerfile-nginx を編集して下さい。
MySQL の別バージョンを使いたい場合は ./docker-compose.yml と ./web/Dockerfile-mysql を編集して下さい。
Django, uwsgi, mysqlclient の別バージョンを使いたい場合は ./web/requirements.txt を編集して下さい。
※Django は3.0がリリースされていますが、テストできておらず 2.2 を対象としています。
※Python 3.8.1 + apine 3.11 ではDjangoアプリ作成後の動作確認だけで、アプリの作成はチェック出来ていません。多分動くのでは無いかと・・・

お約束

  • 個人的なテストとして作成したものでなんら保証が出来るようなものではありません。
  • 試される際は自己責任でお願いします。
  • 私自身の理解が足りておらず「コピペで動いている」ところもあります。
  • とはいえ、この情報が誰かの役に立てばいいなと思っています。

1. 前準備

開発用のソースを GitHub から取得(clone)します

1. プロジェクト用のフォルダを作り移動します

例)home フォルダに myprojects というフォルダを作成してそこに取得する場合。

shell
$ mkdir ~/myprojects/  && cd ~/myprojects/

このフォルダ下に django-ecs-sample というフォルダが作成されます。

2. GitHub からテストプロジェクトを clone します

shell
$ git clone https://github.com/brokenumbrella/django-ecs-sample.git

Windowsで git の改行コードの自動変換がONの場合には注意が必要です
git のインストール方法によっては、改行コードが CR+LF で取得されます。
もし web/run-my-app.sh ファイルを VSCode などのエディターで開いた際に改行コードが CRLF だった場合は、LF に変更して下さい。
linux では CRLF のシェルスクリプトファイルは実行できずエラーとなります。
(私はこれの解決に1日かかってしまいました)

3. clone したフォルダ及びファイルの構成

django-ecs-sample フォルダは下記の構成になっています。

+ db
  + conf
    - mysql_my.cnf                # MySQL8 で使う設定ファイル
+ nginx
  - uwsgi_params                  # uwsgi の設定ファイル
  + conf
    - default.conf                # nginx の設定ファイル
+ web
  - Dockerfile                    # docker-compose でビルドする際に使う Dockerfile
  - Dockerfile-mysql              # ECS へデプロイする際に使う MySQL用 Dockerfile
  - Dockerfile-nginx              # ECS へデプロイする際に使う nginx用 Dockerfile
  - Dockerfile-web                # ECS へデプロイする際に使う Web(Django)アプリ用 Dockerfile
  - requirements.txt              # Python のライブラリ定義ファイル
  - uwsgi.ini                     # uwsgi 用の設定ファイル(プロジェクトフォルダーへコピーします)
  - run-my-app.sh                 # ECS へデプロイする際に使う Webアプリ起動用シェルスクリプト
- .gitignore
- docker-compose.yml              # docker-compose 用ファイル
- docker-compose-ecs.yml          # ECS へデプロイする際の docker-compose ファイル
- docom.sh                        # docker-compose を簡単に利用する為のスクリプト
- esc-params.yml                  # ECS へデプロイする際のタスク定義ファイル
- readme.md                       # 簡単なドキュメント
+ log                             # ログ保存用のフォルダ
  + uwsgi
    - __init__.txt
+ static                          # staticファイル用のフォルダ
  - __init__.txt
+ src                             # プロジェクトを入れるソースフォルダ
  - __init__.txt

作成する Django アプリのソースファイルは ./src/ フォルダに置きます。
log, static, src はdocker-composeする際にマウントする際に必要になるため、Gitリポジトリ上では単なる空フォルダです。
gitで空フォルダを保持するために __init__.txt を入れています。

dockerコンテナ内でのユーザーの追加と変更について

dockerコンテナに(ホストマシンの)現在のユーザーを追加するため linux の id コマンドを使っています。
ユーザー切り替えを行う事で docker 側でマウントしたフォルダに現在のユーザーと同じ権限でファイルやフォルダを作成出来ます。
ユーザーを指定していない場合は root ユーザーでファイルが作られるため、ローカル環境で編集するには root への昇格が必要になります。
そういった手間を減らすためと、docker は root ユーザー以外で運用する方が良いらしいのでこのようにしています。
このため動作確認済みの環境以外では動かない可能性もあります。ご注意下さい。

docom.shについて

上記の id コマンドを使ったユーザーの設定などを簡略化するため docom.sh というスクリプトファイルを同封しました。
docker-compose を起動する際にユーザーID等をセットするためのシェルスクリプトで下記のようになっています。

shell
$ cat docom.sh
DUID=$(id -u) DGID=$(id -g) MYSQL_PW=PWRoot1 docker-compose $1 $2 $3 $4 $5 $6 $7 $8 $9

環境変数の DUID と DGID には id コマンドを使ってユーザーIDとグループID を入れています。
(ちなみに名前ではなく 1000 といった番号です)
また MYSQL_PW=PWRoot1 の部分は MySQL DB のルートユーザーパスワードになっています。
実際に使う際は変更して頂くようお願いします。(このままでも動きますが)
変更の際は web/mydb.cnf(プロジェクトフォルダへコピーしてればそちらも) と docker-compose-ecs.yml にも同じパスワードを設定して下さい。
"PWRoot1" を Grep で一括変換する方が良いと思います。

上記の設定を一時的に環境変数へセットし docker-compose を呼び出しています。
今後はコード簡便化のため、このスクリプトを使って説明していきます。
このスクリプトは開発やテストを目的としており、セキュリティに注意すべき環境では他の方法を検討して下さい。(ここ大事)

2.ローカル環境で Django Polls アプリを動かしてみよう

それでは本題に入りましょう。
docker compose を使いローカル環境で nginx, MySQL, Web(Djangoアプリ) の3つのコンテナを起動して動かし Polls アプリを作ります。

1. web 用の docker image を作成するためビルドします

まず Python+Django 環境を構築するために docker-compose build を行います。
下記コマンドを docom.sh ファイルのあるフォルダで実行します。

shell
$ ./docom.sh build web

初めてビルドする際には時間がかかります。
(ベースコンテナをダウンロードするためインターネットへの接続スピードにも影響されます)
下記のように Successfully built と出ればビルド成功です。

shell
Successfully built 036160743a81
Successfully tagged django-ecs-sample_web:latest

ここでビルドに使うファイルについて簡単に説明します。

(1)Web コンテナをビルドするための Dockerfile です

./web/Dockerfile
FROM python:3.7.4-alpine3.10
#  alpine では apk を使う(add でインストール)
# nginx, suprevisor, uwsgi のインストール(gcc,build-base,linux-headersはuwsgiインストール時に使うためインストールする)
# libffi-dev, mysql-dev, mysql-client, python3-dev は mysql を使うためにインストール
RUN apk update && apk add --no-cache \
    gcc \
    build-base \
    linux-headers \
    libffi-dev \
    mysql-dev \
    python3-dev && \
    pip3 install --upgrade pip

# requirements.txt から Django などの必要なライブラリをインストール
# 不要になった gcc などをアンインストール
COPY ./requirements.txt /code/
RUN pip3 install -r /code/requirements.txt && \
    apk del gcc build-base linux-headers libffi-dev python3-dev

# 8001番ポートを開放する(ことを宣言する)
EXPOSE 8001
# 作業用フォルダを /code/ にする
WORKDIR /code/

# ユーザーを作成してカレントユーザーにする(一般ユーザーで動かすため)
# docker-compose.yml の args に指定した uid と gid を使えるように宣言する
# ユーザー名、グループ名は id と一緒にしておく
ARG DUID
ARG DGID

# ユーザーの作成
RUN addgroup -S -g $DGID $DGID && \
    adduser -S -u $DUID -g $DGID $DUID

# ユーザーを切り替える
USER $DUID

# 実行コマンドは docker-compose.yml の方で指定するためここはシェルを指定しておく
CMD ["/bin/sh"]
  • python:3.7.4-alpine3.10 イメージを元にしています。
    DockerHubに公開されていれば、FROM python:3.7.4-alpine3.10 の部分を変更する事で別バージョンのPythonやalpineを使うことも出来ます。
    但しバージョンによってはテストアプリが動かない可能性もあります。
  • ライブラリのインストール時に必要な gcc 等をインストールしています。
  • nginx との接続用に 8001 番ポートを開けています。
  • /code/ を作業フォルダにしていますが、docker-compose.yml でローカル環境の ./src/ フォルダへマウントします。 つまりこのコンテナ内で /code/ 下に置いたファイルはローカル環境の ./src/ 下に置かれます。 (マウントについては dockerのドキュメント等をご参照下さい)
  • mysql-client は Djangoアプリから MySQL を使うのに必要がないためインストールしていません。
  • ユーザーIDとグループIDを環境変数から取得してユーザーを作成します。

(2)インストールする Python ライブラリを指定するファイルです。

./web/requirements.txt
django>=2.2.10, <3.0       # django 2.2.10 で動作確認済み、2.?.? の間は動くと仮定している
uwsgi>=2.0.18, <3.0       # uwsgi 2.0.18 で動作確認済み、2.?.? の間は動くと仮定している
mysqlclient>=1.4.6, <2.0  # mysqlclient 1.4.6 で動作確認済み、1.?.? の間は動くと仮定している

django, uwsgi, mysqlclient をインストールしています。
こちらで動作確認したのはそれぞれ >= で書かれているバージョンです。
もしアプリが起動しない場合、>= を == に変更し、,以降をコメントアウトする事でバージョンを固定してみて下さい。

例)
django==2.2.10

(3)docker compose で使用するYAMLファイルです。

docker-compose.yml
version: '3'

services:
  db:                                         # MySQL DB 用の設定(この db という名前で web から DB HOST の指定ができる)
    image : mysql:8.0.19
    container_name: mysql.db
    volumes:                                  # マウントフォルダの指定
      - ./db/data:/var/lib/mysql              # データの永続化を行う
      - ./db/conf/:/etc/mysql/conf.d/         # 設定ファイルをここから読み込ませる
      - ./db/sqls:/docker-entrypoint-initdb.d # 初期データを与える場合はここから読み込ませる
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PW}       # rootパスワードの設定
      - MYSQL_DATABASE=mysite                 # 作成するDatabase名
      - TZ=Asia/Tokyo                         # タイムゾーンを日本時間に変更

  web:                                        # Web(Django)アプリケーション 用の設定
    build:                                    # ビルド設定
      context: ./web                          # ./web/Dockerfile を用いてビルドする
      args:                                   # Dockerfile へ渡す環境変数の指定
        - DUID=${DUID}
        - DGID=${DGID}
    environment:                              # 実行時に設定する環境変数
      - MYSQL_HOST=${MYSQL_HOST}
    user: ${DUID}:${DGID}                     # 実行時のユーザー指定
    container_name: django.web
    command: uwsgi --ini /code/mysite/mysite/uwsgi.ini
    volumes:                                  # マウントフォルダの指定
      - ./src:/code                           # アプリケーションのソースフォルダ
      - ./static:/static                      # static ファイルフォルダ
      - ./log/uwsgi/:/var/log/uwsgi           # uwsgi の設定ファイルをここから読み込ませる
    expose:                                   # 開放するポート
      - "8001"
    links:                                    # db コンテナを先に立ち上げてから web を立ち上げる
      - db

  nginx :                                     # nginx 用の設定
    image: nginx:1.17.8
    container_name: nginx
    ports:                                    # 8080 ポートを 80 ポートへ
      - "8080:80"
    volumes:                                  # マウントフォルダの指定
      - ./nginx/conf:/etc/nginx/conf.d        # nginx 用の設定ファイルをここから読み込ませる
      - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params  # uwsgi のパラメータファイルをここから読ませる
      - ./static:/static                      # static ファイルフォルダ
      - ./log/nginx/:/var/log/nginx           # ログファイルをここへ保存する(永続化)
    depends_on:
      - web                                   # web コンテナの後から起動させる

2. Django project を作成します

上記 1. で作った docker 環境を使いプロジェクトを作成します。
django-admin startproject コマンドを使い mysite という名前で新規作成します。
(mysite 以外の名前にする場合は MySQL DB のテーブル名等も変更する必要があり、 grep 検索などで置換して下さい。)

下記コマンドを実行してプロジェクトを作成します。

shell
$ ./docom.sh run web django-admin startproject mysite

コンテナ内で /code/ フォルダに、ローカル環境では ./src/ フォルダにプロジェクトが作成されます。
もし ./src/mysite/ フォルダが作成されていない場合はMySQLサーバーの立ち上げで失敗している可能性があります。
db/data/ フォルダのファイルとサブフォルダを全て削除してやり直してみて下さい。

3. ./web/uwsgi.ini ファイルを ./src/mysite/mysite/ へコピーします

ローカル環境で下記コマンドを使ってコピーします。

shell
$ cp ./web/uwsgi.ini ./src/mysite/mysite/

これは uwsgi 用の設定ファイルです。
コピーする事でコンテナ内では /code/mysite/mysite/uwsgi.ini に配置される事になります。

./web/uwsgi.ini
[uwsgi]
# この prjname に django-admin startproject で作成したプロジェクト名を指定します。
prjname=mysite
basepath=/code/%(prjname)/
chdir=%(basepath)
module = %(prjname).wsgi:application
socket = :8001
wsgi-file = %(basepath)%(prjname)/wsgi.py
logto = /var/log/uwsgi/uwsgi.log
py-autoreload = 1
# usage:
#  このファイルは django-admin startproject の後に /code/prjname/prjname/ へコピーする。

4. nginx の設定ファイルについて

これは nginx 用の設定ファイルです。
このファイルは nginx コンテナを実行する際にマウントさせて読み込ませるためコピーなどは必要ありません。(こんなファイルだという説明だけです)

./nginx/conf/default.conf
upstream django {
  ip_hash;
  server web:8001;
}
server {
  # the port your site will be served on
  listen      80;
  server_name localhost compute.amazonaws.com; # substitute your machine's IP address or FQDN
  charset     utf-8;
  client_max_body_size 75M;   # adjust to taste
  location /static {
    alias /static;
  }
  location / {
    include     /etc/nginx/uwsgi_params; # the uwsgi_params file you installed
    uwsgi_pass  django;
  }
}
  • django との接続に web コンテナの 8001 ポートを使うように指定します。(ここは理解不足です)
  • server_name では localhost と EC2 の 2つを指定しています。実際にはドメインやIPアドレスを指定するようにして下さい。
  • location /static{ alias } で /static へのアクセスを nginx コンテナの /static フォルダにマッピングさせています。
    ここは後ほど collectstatic で /static フォルダに集約させます。
  • location /{} で /static 以外のアクセスを全て django に処理させるようにしています。

5. MySQL DB を使うように設定を行います。

Django はデフォルトで DataBase に sqlite3 を使うようになっています。
ここでは MySQL DB を使わせるように設定を変更します。

  • ./src/mysite/mysite/settings.py を編集します。
./src/mysite/mysite/settings.py
DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.sqlite3',
    'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
  }
}

上記の部分を下記のように書き換えます。

./src/mysite/mysite/settings.py
DB_SETTING_FILE = os.path.join(BASE_DIR,'mydb.cnf')
DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.mysql',
    'OPTIONS':{
      'read_default_file':DB_SETTING_FILE,
    },
  }
}

DB ENGINE には django.db.backends.mysql を指定します。
MySQL 関連の設定は次に説明する mysql.cnf ファイルから読み込むように指定しています。
ついでに、Djangoで使う文字コードを日本語に、時間も日本時間へ変更しておきます。
LANGUAGE_CODE を 'ja' に、TIME_ZONE を 'Asia/Tokyo' に変更します。

./src/mysite/mysite/settings.py
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'
  • ./web/mydb.cnf を ./src/mysite/ へコピーします。
    Django から MySQL DB へアクセスするための設定をこのファイルに纏めています。 コードは下記のようになっています。
./web/mydb.cnf
[client]
  database = mysite
  user = root
  password = PWRoot1
  host = db
  port = 3306
  default-character-set = utf8mb4
  • 'database' には docker-compose.yml の service: db: environment: MYSQL_DATABASE に指定したDataBase名を入れます。
  • 'user' は root ユーザーでアクセスさせています。
  • 'password' には docom.sh で指定した MYSQL_PW の値を指定して下さい。
  • 'host' には docker-compose.yml で定義した service の名前 'db' を指定します。後は docker compose がよしなにやってくれます。
  • 'port' は 一般的なMySQLで使用するポート番号 3306 を設定しています。
  • 'default-character-set' で絵文字などに対応したutf8mb4 を指定しています。

6. docker-compose up でサーバーを立ち上げてアプリケーションを動かします

下記コマンドでサーバーが起動します。
(-d を指定してバックグラウンドでコンテナを実行させています。)

shell
$ ./docom.sh up -d
Starting mysql.db ... done
Starting django.web ... done
Starting nginx      ... done

上記のように3つのコンテナが起動したら、Webブラウザを立ち上げて http://localhost:8080 にアクセスします。
下記の Django のデモ画面が表示されれば問題なくサーバーが起動できています。
おめでとうございます!
django-ecs-sample01.png

7. docker-compose down サーバーを終了します

下記コマンドを実行します。

shell
$ ./docom.sh down
Stopping nginx      ... done
Stopping django.web ... done
Stopping mysql.db   ... done
Removing nginx      ... done
Removing django.web ... done
Removing mysql.db   ... done
Removing network django-ecs-sample_default

上記のようにコンテナが停止・削除されます。
これ以降は http://localhost:8080 へアクセスしてもエラーが返されます。

8. ログファイルはローカル環境の ./log/ フォルダ下に保存されています

サーバーが起動しない等の場合は下記フォルダのログを見て原因を調べることが出来ます。

./log/nginx/ : nginx が出力するログ
./log/uwsgi/ : uwsgi が出力するログ

9. Django アプリケーションを作ります

ここで作るアプリケーションはチュートリアルの polls です。
また以下2つの方法があります。どちらでも好きな方法で作る事が出来ます。

  • 方法1:サーバーを起動させて docker exec によりコンテナ内に入って作業する

  まずサーバーを起動していない場合は起動させます。

shell
$ ./docom.sh up -d

  サーバーが立ち上がったら docker exec で django.web に shell で接続します。
  (Windows10 の場合は winpty をつけて下さい)

shell
$ docker exec -it django.web sh
※windows10 の場合
$ winpty docker exec -it django.web sh

/code $ とプロンプトが出ればコンテナ内に入れています。
  misite プロジェクトフォルダ上で python manage.py startapp を実行します。

shell
/code $ cd mysite
/code $ python manage.py startapp polls
/code $ exit

  ※exit でコンテナから抜けます。

  起動したサーバーを停止します。

shell
$ ./docom.sh down
  • 方法2:docker-compose run web で作る場合

アプリケーションの作成は manage.py のあるフォルダで行うためワークフォルダを指定する必要があります。
docker-compose run -w //code/mysite/ で指定できるので、これを使います。
(ubuntu では /code/mysite/ でも動いたのですが、Windows10 では //code/mysite/ でなければ動きませんでした。)

shell
$ ./docom.sh run -w //code/mysite/ web python manage.py startapp polls

どちらの方法でもアプリケーションを作成できます。
ローカルに ./src/mysite/polls フォルダが出来ていればアプリケーションの作成は成功です。

10. アプリケーションへアクセスできるようにビューとurlsを指定します

ここからは Django の Polls チュートリアルとほぼ同様のコーディング作業になります。

(1) ./src/mysite/polls/views.py を下記のように編集します。

./src/mysite/polls/views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.") 

(2) ./src/mysite/polls/ フォルダに urls.py ファイルを作成し、下記コードを書きます。

./src/mysite/polls/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

(3) 次に ./src/mysite/urls.py ファイルを下記のように書き換えます。

./src/mysite/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]

このルーティングによって http://localhost/polls/ へアクセスする事で 1 の inxex(request) が呼び出されレスポンスが返されるようになります。

11. ここまで出来た Polls アプリを動かしてみましょう

下記コマンドでサーバーを立ち上げます。

$ ./docom.sh up -d

Webブラウザで http://localhost:8080/polls へアクセスします。
URL には /polls を付けて下さい。先ほどと同じlocaphost:8080だけでは Page not found(404) エラーが出ます。
ブラウザ画面に

  Hello, world. You're at the polls index.

と表示されていればここまでは成功です。
サーバーを停止させます。

shell
$ ./docom.sh down

12. collectstatic でスタティックファイルを所定のフォルダへ集めます

AWS ECS などへ公開する場合は Javascript や css 、画像などの static ファイルを1箇所にまとめる必要があります。
また、今回 nginx で /static/ をホストしているためここで纏めておきます。
これらのファイルを纏めるために django では collectstatic が用意されています。
collectstatic を実行するには前準備をする必要がありますので、そこから説明します。

(1). はじめに ./src/mysite/mysite/settings.py の最後に STATIC_ROOT の設定を追記します。
これを入れ忘れると collectstatic で下記のエラーが出ますので、これが出たらここに戻って確認して下さい。

django.core.exceptions.ImproperlyConfigured: You're using the staticfiles app without having set the STATIC_ROOT setting to a filesystem path.

では下記のように追記します。

./src/mysite/mysite/settings.py
  |
  〜いろいろ〜
  |
  STATIC_URL = '/static/'
  STATIC_ROOT = STATIC_URL      # これを追加する

(2). collectstatic を行います。
サーバーが立ち上がっていなければ起動します。

$ ./docom.sh up -d

docker exec でコンテナに入ります。(ubuntu の場合)

$ docker exec -it django.web sh

(Windows10 の場合は winptyをつけて)

$ winpty docker exec -it django.web sh

/code $ とプロンプトが出ればコンテナ内に入れています。

/code $ cd mysite
/code $ python manage.py collectstatic
/code $ exit

もしくは docker-compose run を使い以下の1行でも作成可能です。

$ ./docom.sh run -w //code/mysite/ web python manage.py collectstatic

./static フォルダにファイルが存在する場合は途中で下記のような問いが出ますので、yes を入力して下さい。

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel:

./static フォルダに admin フォルダが追加されている事を確認できると思います。

13. Djangoチュートリアルに従い Polls アプリを作成します

ここからは、下記公式サイトのチュートリアルのその2から7までを実際に行います。
(「高度なチュートリアル」はやらなくても大丈夫です。)
はじめての Django アプリ作成、その2 モデルの作成

チュートリアル内で python manage.py 〜 という部分が出てきた際は「9.Django アプリケーションを作ります」と同じように docker exec もしくは docker-compose run を使います。
例えば python manage.py makemigrations polls を行う場合下記の手順で実行してください。
サーバーが立ち上がっていなければ起動します。

$ ./docom.sh up -d

ubuntu の場合

$ docker exec -it django.web sh

Windows10 の場合

$ winpty docker exec -it django.web sh

/code $ とプロンプトが出ればコンテナ内に入れています。

/code $ cd mysite
/code $ python manage.py makemigrations polls
/code $ exit

./docom.sh up -d はサーバーを起動してない場合に必要です。起動していれば docker exec ~ から実行してください。
またサーバーを起動しておけば、途中でもブラウザの表示更新(リロード)するだけ変更が適用されますので都度サーバーの起動、終了を行う必要はありません。
もしくは

$ ./docom.sh run -w //code/mysite/ web python manage.py makemigrations polls

でも行うことができます。
ただし残念ながら、superuser を作るコマンド python mange.py createsuperuser だけは docker-compose run では実行できません。ここではユーザー名などの入力を求められるからです。このため createsuperuser を行う際は docker exec 〜 を使って下さい。
それから、python shell を実行させている際にソースを修正しても起動している shell には反映されません。
shell を起動しなおす必要があります。
(起動しなおしたら必要であれば from datetime などの初期化処理からやり直します)

ここを完了しなくても次の「2.AWS ECS へデプロイしよう」へ進みデプロイする事は出来ます。
"Hello, world. You're at the polls index."と出るだけですが・・・

2.AWS ECS へデプロイしよう

1. AWS 側の準備

(1) Python をインストールします

AWS CLI をインストールするに先立ち、Python 2.7以降か 3.4 以降が必要になるためインストールします。
既にインストール済みの場合は 2. へ進んでください。
もし Python がインストールされているかわからない場合は、下記コマンドで確認出来ます。

$ python --version

Python 3 の場合は

$ python3 --version

バージョン番号が出ればインストールされています。(但し 2.7 or 3.4 未満の場合はアップデートが必要です)
ここでは Python3 の最新版をインストールします。
Python公式ページhttps://www.python.org/からダウンロード、インストールします。
Windows10 へインストールする場合はこちらも参考にさせていただきました。
Python3のインストール
今回は最新安定板の3.8.1をインストールしました。
インストールが完了すると下記のようにして確認できます。

$ python --version
Python 3.8.1

(2) AWS CLI のインストールと設定

AWS CLI の詳細は下記を参照してください。
AWS コマンドラインインターフェイス

インストールと設定は下記公式ページの手順で行ってください。
2020年2月現在では バージョン2は評価版での公開のため、バージョン1の方をインストールします。

AWS CLI のインストール

Windows10 では下記のページから MSI インストーラをダウンロードしてインストールしました。
また、Windows10では下記のドキュメントに従い PowerShell を使います。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-windows.html#install-msi-on-windows

続けて設定を行います。
AWS CLI の設定

これ以降の説明では「AWS CLI のかんたん設定」の aws configure を実行したとして進めます。
もし「複数のプロファイルの作成」で profile 名を指定した場合は aws --profile profuser ecr 〜〜 とプロファイル名を指定してコマンドを実行してください。(profuser の部分には作成したプロファイル名を指定します)

(3) ECS CLI のインストールと設定

ECS CLI の詳細は下記を参照してください。
AWS ECS コマンドラインインターフェースの使用
インストールは下記公式ページの手順で行ってください。
現時点では Version2 はまだ評価版なので、Version1 をインストールします。
また、ここでは下記のドキュメントに従い Windows10 では PowerShell を使います。

Amazon ECS CLI のインストール

ここも公式にお任せでOKかと思ったのですが、Windows でステップ2の「MD5 サムを使用した検証」をやる場合は注意が必要です。
(オプションなので飛ばす人はこの注意も必要ないです)
まずは手順通りに実行してみます。

ps c:\> Get-FileHash ecs-cli.exe -Algorithm MD5
Resolve-Path : パス 'C:\ecs-cli.exe' が存在しないため検出できません。
~云々~

のように、ecs-cli.exe ファイルが存在しないとエラーが出ました。
これは、ステップ1で C:\Program Files\Amazon\ECSCLI フォルダに ecs-cli.exe をダウンロードしているためです。
このためフルパスを指定して実行します。

PS C:\> Get-FileHash "C:\Program Files\Amazon\ECSCLI\ecs-cli.exe" -Algorithm MD5

これでハッシュ値がとれたと思います。
また、確認のためダウンロードした 'md5.txt' ファイルは確認が済めば不要なので削除して構いません。

2. docker image をビルドします

(1) ./src/mysite/mysite/settings.pyを修正します

ALLOWED_HOST に何処でも動作するよう '*' を指定しておきます。
運用するサーバーが決まったらここには hogehoge.com や '100.100.100.100' などドメインやIPアドレスを指定します。
また、実運用の際には DEBUG = False にしましょう。

./src/mysite/mysite/settings.py
  〜いろいろ〜

  ALLOWED_HOSTS = ['*']       # ここ

  〜いろいろ〜

(2) MySQL, nginx, web の設定やソースコードを含めた Dockerfile について説明します。

前半のローカル環境では MySQL, nginx の設定ファイルを docker の volumes を使って指定しましたが、ECS上で動かすにはコンテナ内にそれらも含めます。
また web の docker image には作成したアプリケーションのソースファイル(./src/ フォルダ以下)を含める必要もあります。
そのため、デプロイ用に web, MySQL, nginx 用 の Dockerfile を作成し、それぞれにビルドします。
各 Dockerfile は ./web/ フォルダに入っていますが、下記のようになっています。

./web/Dockerfile-mysql
FROM mysql:8.0.19

# 設定ファイルをコピーする
COPY ./db/conf/ /etc/mysql/conf.d/
./web/Dockerfile-nginx
FROM nginx:1.17.8

# 設定ファイルをコピーする
COPY ./nginx/conf /etc/nginx/conf.d
COPY ./nginx/uwsgi_params /etc/nginx/uwsgi_params
# static ファイルは nginx で返すためこのコンテナにコピーしておく
COPY ./static /static
./web/Dockerfile-web
FROM python:3.7.4-alpine3.10

# alpine では apk を使う(add でインストール)
# nginx, suprevisor, uwsgi のインストール(gcc,build-base,linux-headersはuwsgiインストール時に使うためインストールする)
# libffi-dev, mysql-dev, python3-dev は mysql を使うためにインストール
RUN apk update && apk add --no-cache \
  gcc \
  build-base \
  linux-headers \
  libffi-dev \
  mysql-dev \
  python3-dev && \
  pip3 install --upgrade pip

# requirements.txt から Django などの必要なライブラリをインストール
# 不要になった gcc などをアンインストール
COPY ./web/requirements.txt /code/
RUN pip3 install -r /code/requirements.txt && \
    apk del gcc build-base linux-headers libffi-dev python3-dev

# ソースファイルを /code/ フォルダへコピーする
COPY ./src/ /code/
# 実行時のスクリプトファイルをコピーする
COPY ./web/run-my-app.sh /code/

# 8001番ポートを開放する
EXPOSE 8001
# 作業用フォルダを /code/ にする
WORKDIR /code/

# ユーザーを作成してカレントユーザーにする(一般ユーザーで動かすため)
# docker-compose.yml の args に指定した uid と gid を使えるように宣言する
# ユーザー名、グループ名は id と一緒にしておく
ARG DUID
ARG DGID

# ユーザーの作成
RUN addgroup -S -g $DGID $DGID && \
    adduser -S -u $DUID -g $DGID $DUID

# uwsgi 用のログパスを追加
RUN mkdir /var/log/uwsgi/
RUN chown -R $DUID:$DGID /var/log && \
    chown -R $DUID:$DGID /code

USER $DUID

(3) 3つのDockerfileをビルドして docker image を作ります

今回は docker コマンドの build を使います。
前半の docker-compose ではないため docom.sh は使いません、ご注意ください。
下記のコマンドでビルドを行います。

$ docker build -t django-ecs-sample-web -f ./web/Dockerfile-web --build-arg DUID=$(id -u) --build-arg DGID=$(id -g) .
$ docker build -t django-ecs-sample-mysql -f ./web/Dockerfile-mysql .
$ docker build -t django-ecs-sample-nginx -f ./web/Dockerfile-nginx .
  • -t django-ecs-sample-web など、それぞれのイメージに対して今後利用しやすいように名前をつけています。
  • -f でビルドに使う Dockerfile を指定しています。
  • --build-arg DUID=$(id -u) --build-arg DGID=$(id -g) の部分はdocom.shで環境変数をdocker-composeへ渡していたのと同じです。 docker build では --build-arg オプションで環境変数を渡すところに注意が必要です。

3. docker-compose-ecs.yml について

ecs へデプロイするために専用ファイルを用意しました。
また各 image には仮の URL を指定しています。
後ほど ECR にリポジトリを作成して、そのURLに書き換えます。

docker-compose-ecs.yml
version: '3'

services:
  db:
    image : XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-mysql:latest
    volumes:
      - /db/data:/var/lib/mysql              # データの永続化を行う
    environment:
      - MYSQL_ROOT_PASSWORD=PWRoot1
      - MYSQL_DATABASE=mysite
      - TZ=Asia/Tokyo

  web:
    image: XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-web:latest
    command: sh /code/run-my-app.sh
    links:
      - db

  nginx :
    image: XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-nginx:latest
    ports:
      - "80:80"
    links:
      - web

この中で webコンテナの起動コマンドを下記のように変えています。
web: command: sh /code/run-my-app.sh
これは django の migrate で DB へのマイグレーション処理を行ってから uwsgi を起動するスクリプトです。
初回起動時に MySQL の準備が間に合わず migrate に失敗する事から成功するまで無限ループさせています。
無限ループは正直恐ろしいんですが、これに失敗するって事は MySQL が立ち上がらないということなので、いいかなと。
実際には他にもっとスマート(本来の)やり方があるんじゃないかと思います。
そもそも RDS などのマネージドサービス使えば前もって起動させておき、migrate なども別のタイミングで行うことが出来るはず、などなどのご意見もあると思いますが。
どなたかこの場合に他に何か良策があれば教えて頂けると助かります。
ともかく先へ進めるため下記のスクリプトを動かすようにしました。

./web/run-my-app.sh
#!/bin/sh

app=mysite
while :
do
  if python /code/$app/manage.py migrate; then
    break
  else
    sleep 1
  fi
done
uwsgi --ini /code/$app/$app/uwsgi.ini

exit 0

4. AWS ECR にリポジトリを作成し、dockerイメージをプッシュします

ECS へデプロイする際 docker image は ECR(Elastic Container Registry) か docker hub に置く必要があります。
今回は AWS で完結させたいので、先程ビルドした3つの docker image を AWS の ECRへプッシュします。

(1) AWS ECR にプッシュ用のリポジトリを作成します

始めに下記コマンドで、web 用の django-ecs-sample-web リポジトリを作成します

shell
$ aws ecr create-repository --repository-name django-ecs-sample-web

作成に成功すれば下記のようにJSON形式で作成したリポジトリの情報が返されコンソール画面に表示されます。

json
{ 
  "repository": {
    "repositoryArn": "arn:aws:ecr:ap-northeast-1:XXXXXXXXXXXX:repository/django-ecs-sample-web",
    "registryId": "XXXXXXXXXXXX",
    "repositoryName": "django-ecs-sample-web",
    "repositoryUri": "XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-web",
    "createdAt": 1575769798.0,
    "imageTagMutability": "MUTABLE",
    "imageScanningConfiguration": {
      "scanOnPush": false
    }
  }
}

この時に返される"repositoryUri"の"XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-web"を控えておきます。
もしくは、URL の構成は
[AWS ACCOUNT ID].dkr.ecr.[reagion].amazonaws.com/[repository name]
のようになっているので、アカウントID, リージョン名, リポジトリ名 から導き出すことも出来ます。(今回のリポジトリ名は django-ecs-sample-web です。)
これは push 用のタグ付けや docker-compose-ecs.yml でイメージの読み込み先として使うため後々必要になります。
続いて nginx、mysql 用のリポジトリも作成し、同様にrepositoryUriを控えます。

shll
$ aws ecr create-repository --repository-name django-ecs-sample-nginx
$ aws ecr create-repository --repository-name django-ecs-sample-mysql

(2) ./docker-compose-ecs.yml ファイルを修正します

リポジトリが出来たので、./docker-compose-ecs.yml ファイルの image 項目をリポジトリ URI に書き換えます。
./docker-compose-ecs.yml を開くと下記の用に XXXXXXXXXXXX.dkr.ecr となっていますので、ここを(1)で控えた URI に書き換えてください。(もしくはデプロイするアカウントIDをXXXXXXXXXXXXに入れるので構いません。)

./docker-compose-ecs.yml
  mysql:
    image: XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-mysql:latest

  web:
    image: XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-web:latest

  nginx:
    image: XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-nginx:latest

(3) ECR へプッシュできるよう docker image にタグを付けます

下記のように docker tag コマンドを使用します。
XXXXXXXXXXXX の URI 部分は、リポジトリ作成時に控えた repositoryUri の値を指定して下さい。

shell
  $ docker tag django-ecs-sample-nginx:latest XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-nginx:latest
  $ docker tag django-ecs-sample-web:latest XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-web:latest
  $ docker tag django-ecs-sample-mysql:latest XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-mysql:latest

タグが付いたかは下記のようにして確認出来ます。

  $ docker images
  REPOSITORY                                                                TAG       IMAGE ID        CREATED       SIZE
  XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-nginx   latest    1dd8c2bc8f1d    1 days ago    127MB
  django-ecs-sample-nginx                                                     latest    1dd8c2bc8f1d    1 days ago    127MB
  XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-web     latest    7633e0977266    1 days ago    462MB
  django-ecs-sample-web                                                       latest    7633e0977266    1 days ago    462MB
  XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-mysql   latest    fdbaa71f3406    1 days ago    456MB
  django-ecs-sample-mysql                                                     latest    fdbaa71f3406    1 days ago    456MB

(4) ECR リポジトリにへプッシュします

下記のように ecs-cli push コマンドを使います。
XXXXXXXXXXXX の URI 部分は、リポジトリ作成時に控えた repositoryUri の値を指定して下さい。

shell
$ ecs-cli push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-web:latest
INFO[0000] Pushing image       repository=XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-nginx tag=latest
INFO[0015] Image pushed

のように表示されれば成功です。
また下記はインターネット接続を切った時に出たエラーメッセージです。
何らかのエラーが出るとこのように表示されるので適宜対応してください。

FATA[0000] Error executing 'push': unable to create repository: RequestError: send request failed
caused by: Post https://api.ecr.ap-northeast-1.amazonaws.com/: dial tcp: lookup api.ecr.ap-northeast-1.amazonaws.com: no such host

続けて nginx と mysql のコンテナも push します。

  $ ecs-cli push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-nginx:latest
  $ ecs-cli push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-mysql:latest

5. AWS ECS にクラスターを作成します

Amazon ECS クラスターとは、タスクまたはサービスの論理グループです。
EC2 を使用してタスクまたはサービスを実行している場合、クラスターはコンテナインスタンスのグループ化でもあります。
とのことですが、私は単純にインスタンス、サービス、タスクの容れ物だと思って使っています。
詳細は下記の公式サイトを参照して下さい。
Amazon ECS クラスター

(1) ecs-params.yml ファイルを作成します

Amazon ECS タスク定義にはdocker-compose.ymlには対応しないフィールドがあり、それを ecs-params.yml で指定します。
今回はリポジトリ内に用意しているためそれを使います。
ここでは無料枠があれば無料となる t2.micro マシンを使いたいのでメモリーサイズを調整しています。
このファイルが無い場合メモリーサイズは各コンテナに 500MB ずつ割り当てられるようです。
つまり3つコンテナを立ち上げるには 1.5GB 以上のメモリーを持ったEC2インスタンス(t3.smallやt2.smallなど)が必要になります。
t2.micro は残念がら 1GB しかメモリーがないので調整が必要ということです。
また、逆にもっと大きなメモリーサイズのEC2インスタンスを立ち上げて大量のメモリーを割り当てる場合にも設定が必要です。
但し下記は t2.micro で動かせたというだけで最適化は行っていません。

ecs-params.yml
version: 1
task_definition:
  services:
    nginx:
      mem_limit: 150m
      mem_reservation: 128m
    web:
      mem_limit: 448m
      mem_reservation: 400m
    db:
      mem_limit: 448m
      mem_reservation: 400m

ファイル名が ecs-params.yml なら自動的に読み込まれますが、他の名前を付ける場合は、--ecs-params にパス名を指定します。
詳細は下記の公式ドキュメントを参照して下さい。
Amazon ECS パラメータの使用

(2) ECS クラスター設定を作成します

この設定ではクラスター用のインスタンスの種類などを指定します。
ECS CLI を使い下記のコマンドを実行する事でクラスター設定を作成できます。

$ ecs-cli configure --cluster django-ecs-sample --default-launch-type EC2 --config-name django-ecs-sample --region ap-northeast-1

指定しているパラメータについて

  • --cluster : ここにはクラスター名を指定します。
  • --default-launch-type : ここには作成するインスタンスの種類を指定します。 EC2 を指定すると EC2インスタンスを立ち上げて docker 環境を構築します。 サーバレスの Fargate を指定することもできます。
  • --config-name : 作成する config の名前を指定します。
  • --region : EC2を立ち上げるリージョンを指定します(例 ap-northeast-1)

下記のように返ってくれば django-ecs-sample という名前のクラスター設定が保存され、使えるようになります。

INFO[0000] Saved ECS CLI cluster configuration django-ecs-sample.

(3) ECS クラスターを作成します

  • EC2 keypair を用意します。 superuser を作成する際に ssh で接続するため、ここで作成しておきます。 既存のキーペアーを持っていて使える場合はここを飛ばしても大丈夫です。 下記のコマンドを実行します。
shell
$ aws ec2 create-key-pair --key-name MyKeyPair --query 'KeyMaterial' --output text > MyKeyPair.pem

  'MyKeyPair' と 'MyKeyPair.pem' の部分にはわかりやすい名前を指定します。
  出来た pem ファイルを ~/.ssh/ フォルダへ保存しておきます。

shell
$ mkdir ~/.ssh
$ mv ./MyKeyPair.pem ~/.ssh

  また、作成した pem ファイルにはアクセス制限をかけておく事が推奨されていますので下記コマンドを実行します。(ubuntuの場合のみ)

shell
$ chmod 400 ~/.ssh/MyKeyPair.pem

詳細は下記の公式ページを参照して下さい。
Amazon EC2 キーペアの作成、表示、削除

  • ECS クラスターを作成します。 ここでは t2.micro のEC2インスタンスを1つ作ります。 (無料枠を使えない場合は t3.micro の方が安いのでそちらでも構いません。)
shell
$ ecs-cli up --keypair MyKeyPair --capability-iam --size 1 --instance-type t2.micro --cluster-config django-ecs-sample --ecs-profile default

  'MyKeyPair' には先ほど作成した keypair 名を指定します。
  "Cluster creation succeeded."` と表示されれば成功です。
  起動には数分間かかりますので気長に待ちましょう。
  またここでEC2インスタンスが立ち上がります。
  一応最初の1年間は無料枠となっているインスタンスを使っていますが、他にもインスタンスを立ち上げている等々条件によって無料にならない場合もあります。
  ECSクラスターの破棄を行うまでは課金されますので中断する場合などはご注意ください。
  下記のように表示されればクラスターの作成は成功です。

shell
VPC created: vpc-043b9c071191494f9
Security Group created: sg-0289045419da1393c
Subnet created: subnet-04f7f97d1b133656a
Subnet created: subnet-0d323c836901d8e40
Cluster creation succeeded.

  何らかの理由でクラスターの作成をやり直す場合は、一旦下記のコマンドでクラスターを削除してからやり直してください。

shell
$ ecs-cli down --force --cluster-config django-ecs-sample

6. 作成したクラスターにサービスを作成し、アプリケーションをデプロイします

下記コマンドでサービスを作成してデプロイします。

shell
$ ecs-cli compose --file docker-compose-ecs.yml service up --cluster-config django-ecs-sample

"ECS Service has reached a stable state" と表示されればデプロイ成功です。
以下のコマンドで実行しているタスクを確認できます。

shell
$ ecs-cli compose service ps
Name                                        State    Ports                    TaskDefinition     Health
26f9f2f5-7ff2-4f59-9852-1299c9572a59/nginx  RUNNING  3.113.1.25:80->80/tcp  django-ecs-sample:5  UNKNOWN
26f9f2f5-7ff2-4f59-9852-1299c9572a59/db     RUNNING                         django-ecs-sample:5  UNKNOWN
26f9f2f5-7ff2-4f59-9852-1299c9572a59/web    RUNNING                         django-ecs-sample:5  UNKNOWN

上記の場合 nginx の Ports に出力されている 3.113.1.25 がアクセスするIPアドレスとなります。
ブラウザを使ってアクセスするために控えておいて下さい。

7. Webブラウザで動作確認を行います

ChromeやFirefoxなどWebブラウザを起動し、上記で確認した nginx の Ports アドレスを指定します。

Chrome,Firefox
http://3.113.1.25/polls
※上記のURI 3.113.1.25 の部分はnginxのPortsアドレスに置き換えて下さい。

ブラウザ上に下記のように表示されれば成功です。

No polls are available.

もしも

OperationalError at /polls/
(2002, "Can't connect to MySQL server on 'db' (115)")

のようなエラーが出た場合は MySQL との接続がうまく行っていない可能性が高いです。
mydb.cnf の password と docker-compose-ecs.yml の MYSQL_ROOT_PASSWORD が同じか確認して下さい。
上記が違っていた場合は docker-compose-ecs.yml の MYSQL_ROOT_PASSWORD を mydb.cnf の password に合わせてください。
修正後、下記「後始末(サービスとリソースを削除する)」の1,2を実行してサービスとクラスターを削除した後、クラスターの作成からやり直してみて下さい。

8. ssh で AWS EC2 へログインし createsuperuser を行います

7 で No polls are available. と表示されれば MySQL との接続も問題なくアプリは完成!
と言いたい所ですが・・・
残念ながら superuser を作成していないため /admin でログインする事が出来ず Questions 等のデータを作成する事も出来ません。
どうしても createsuperuser を上手く実現する方法を思いつけず、苦し紛れの ssh 接続で対応する事にします。

手順

(1) EC2 のセキュリティグループのインバウンドで ssh を開放します

この設定はAWSコンソール上で行います。
Webブラウザを立ち上げてAWSアカウントへログインします。
EC2 コンソール画面を表示させ、ECS用に作成された EC2 インスタンスを選択します。
(もしくは ECS コンソール画面からインスタンスを選択して EC2 コンソールを出す事も出来ます。)
下記画像のインスタンスの詳細の右側赤枠にある「パブリック DNS (IPv4)」の値(ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.com)を控えておきます。
続いて左下赤枠にあるセキュリティグループのリンクをクリックしてセキュリティグループ画面を表示します。

django-ecs-sample02.png

セキュリティグループの詳細で「インバウンド」タブを選択、「編集」ボタンをクリックします。

django-ecs-sample03.png

インバウンドルールの編集画面で、ルールの追加ボタンをクリックします。
タイプを「SSH」にしソースで「マイIP」を選択して保存をクリックします。
固定IPアドレスを使っている場合はIPアドレスが変わらないためこのままで問題ありませんが、それ以外の方は速やかに進めて下さい。

django-ecs-sample04.png

(2) ターミナル(もしくはGitBash)に戻り、ssh 接続を行います

下記のコマンドで ssh 接続を行います。
MyKeyPair.pem には クラスター作成時に作ったものを指定します。
また、ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.comの部分には上記 (1) で控えたパブリック DNS (IPv4)を入れます。
ec2-user というユーザー名は Amazon Linux 2 または Amazon Linux AMI を使った際に決まっているユーザー名です。
この辺りは SSH を使用した Linux インスタンスへの接続 を参照して下さい。

shell
$ ssh -i ~/.ssh/MyKeyPair.pem ec2-user@ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.com

初めて ssh 接続する際は下記のような問いがでるので、yes と入力します。

shell
The authenticity of host 'ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.com (XX.XX.XX.XX)' can't be established.
ECDSA key fingerprint is SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Are you sure you want to continue connecting (yes/no)?

ログインに成功すると下記のようなプロンプト画面になります。

shell
[ec2-user@ip-10-0-1-38 ~]$

続いて起動している web コンテナのIDを調べます。

shell
$ docker ps
CONTAINER ID        IMAGE                                                                              COMMAND                  CREATED             STATUS                    PORTS                 NAMES
9b923b483350        XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-nginx:latest   "nginx -g 'daemon of…"   40 minutes ago      Up 40 minutes             0.0.0.0:80->80/tcp    ecs-django-ecs-sample-20-nginx-94c0f0a1f7c3e09a2200
8f06e3da926f        XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-web:latest     "sh /code/run-my-app…"   40 minutes ago      Up 40 minutes             8001/tcp              ecs-django-ecs-sample-20-web-e69bd295ee81ad978201
719a8da19a44        XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/django-ecs-sample-mysql:latest   "docker-entrypoint.s…"   41 minutes ago      Up 41 minutes             3306/tcp, 33060/tcp   ecs-django-ecs-sample-20-db-96eacce1d2fba3c8f501
862ad405bf2b        amazon/amazon-ecs-agent:latest                                                     "/agent"                 41 minutes ago      Up 41 minutes (healthy)                         ecs-agent  

上記の場合 django-ecs-sample-web:latest のタグが付いているコンテナIDは 8f06e3da926f なので、docker exec を使ってこのコンテナに入ります。

shell
$ docker exec -it 8f06e3da926f sh

/code $ とプロンプトが変わります。
これ以降ssh接続中の説明では $ 以前はプロンプトです。$ 以降をコマンドとして入力して下さい。
cd mysite でフォルダを /code/mysite/ へ移動します。念の為manege.pyがあるか ls コマンドで調べます。

shell
/code $ cd mysite
/code/mysite $ ls -l
total 20
-rwxr-xr-x    1 197612   197121         626 Jan 18 09:51 manage.py
-rwxr-xr-x    1 197612   197121         119 Jan 18 09:54 mydb.cnf
drwxr-xr-x    1 197612   197121        4096 Jan 18 09:56 mysite
drwxr-xr-x    1 197612   197121        4096 Jan 20 14:51 polls      

manage.py がありましたね。

それでは下記コマンドでスーパーユーザーを作成しましょう。

shell
/code/mysite $ python manage.py createsuperuser
ユーザー名 (leave blank to use '1000'): Admin
メールアドレス: Admin@test.com
Password:
Password (again):
Superuser created successfully.

上記のように、ユーザー名、EMailアドレス、パスワードの入力を求められるので適宜入力します。
(上記はサンプルです。)

shell
Superuser created successfully.

と出れば作成完了です。
docker コンテナと EC2 から exit でログアウトします。

shell
/code/mysite $ exit
[ec2-user@ip-10-0-1-38 ~]$ exit
ログアウト
Connection to ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.com closed.

上記のように表示されれば ssh を使った作業は全て完了です。

(3) セキュリティグループに追加した ssh の設定を削除します

先程の AWS コンソールに戻りインバウンドルールの編集画面で下記の画像の赤枠✖をクリックし、保存します。
django-ecs-sample05.png

これで晴れて /admin でログインし、Questions と Choices を作成して Polls アプリを動かすことが出来ます。
お疲れ様でした!

9. 後始末(サービスとリソースを削除しておきましょう)

テストが終わったら無駄な課金をさけるためリソースを削除しておきます。
下記の手順で削除していきます。

(1) サービスを削除します

shell
$ ecs-cli compose --file docker-compose-ecs.yml service rm

(2) クラスターを削除します

shell
$ ecs-cli down --force
〜〜〜
  INFO[0121] Deleted cluster                               cluster=django-ecs-sample

という表示が出れば無事クラスターが削除されています。

(3) ECR イメージを削除します

ECR も課金されますので、使わなくなったら削除しておきましょう。(微々たる金額ですが)

shell
$ aws ecr batch-delete-image --repository-name django-ecs-sample-web --image-ids imageTag=latest
$ aws ecr batch-delete-image --repository-name django-ecs-sample-nginx --image-ids imageTag=latest
$ aws ecr batch-delete-image --repository-name django-ecs-sample-mysql --image-ids imageTag=latest

(4) ECR リポジトリを削除します

shell
$ aws ecr delete-repository --repository-name django-ecs-sample-web
$ aws ecr delete-repository --repository-name django-ecs-sample-nginx
$ aws ecr delete-repository --repository-name django-ecs-sample-mysql

ここまで削除すれば課金されることはありません。
但し、ecs-cli push を複数回行った場合など latest 以外のイメージがあるとリポジトリの削除に失敗する事があります。
そのような場合は AWS コンソールから ECR リソースを確認して削除してください。

(5) タスク定義を登録解除します

タスクも不要なのでリストアップされないようにしておきます。
まず下記コマンドでタスク定義のリストを表示させます。

shell
$ aws ecs list-task-definitions

下記のようにタスク定義が表示されます。

shell
{
    "taskDefinitionArns": [
        "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/django-ecs-sample:1",
        "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/django-ecs-sample:2",
        "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/django-ecs-sample:3",
        "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/django-ecs-sample:4"
    ]
}

今回いろいろテストしたためタスク定義が4つも作られていました(1度で成功した場合は1つしか作られません)。
これはタスク設定に変更があった場合 compose service up の度に新しいものが作成されるようです。
これら全てが不要ですので、下記のコマンドで登録解除します。

shell
$ aws ecs deregister-task-definition --task-definition django-ecs-sample:1

django-ecs-sample:1 の部分に上記リストのタスク名とリビジョンを入れます。
登録解除されれば解除されたタスク情報が返されます。
全てのリビジョンを登録解除して再び list-task-definitions で確認すると

shell
{
    "taskDefinitionArns": [
    ]
}

と、登録解除された事がわかります。
先程から登録解除と書いているようにあくまで解除されただけで削除されたわけではありません。
下記のコマンドで INACTIVE なタスク定義をリストアップさせると、

shell
$ aws ecs list-task-definitions --status INACTIVE
{
  "taskDefinitionArns": [
      "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/django-ecs-sample:1",
      "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/django-ecs-sample:2",
      "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/django-ecs-sample:3",
      "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/django-ecs-sample:4",
  ]
}

上記のように先程登録解除した django-ecs-sample:1 などが列挙されました。
これは既に動いているクラスターやサービスのタスク定義を登録解除してもタスクが終了するわけでは無いことを表しています。
またこれら登録解除済みのタスクを使っているクラスターのインスタンスが再起動した場合でも解除済みのタスク定義でタスクは起動します。
では何が違うのかと言えば、解除したタスク定義を使って新規でタスクを立ち上げる・サービスを更新する事は出来ないという意味です。
せめて何処でも使ってないタスク定義(今回のテストプログラムのようなもの)は削除したいと思いますが、今の所タスク定義を完全に削除する方法は無さそうです。

あとがき

最後までお付き合い頂きありがとうございました。
私がよく理解できてないため解りにくい所もあったかと思います。
また、デプロイ後にコードを修正したりアップデートする方法については書けていません、調べてやってみて頂ければと思います。

最初にも書きましたが、こちらは私が作ったDjangoのテストアプリを ECS へ出来るだけ簡単にデプロイする為に作ったサンプルを元にしています。
そのため私がいかに早く楽にデプロイできるかに重点を置きました。
本来重要なセキュリティなどは置き去りとも言えます・・・

なんにせよ docker と ECS を使えば簡単に Django アプリを AWS 上で動かせるんだなと思って頂けたらと思います。

最後になりましたが、これを書くにあたって沢山の記事、勉強会での発表等を参考にさせて頂きました。
それらに係わられた全ての方に感謝しています、ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

そろそろaws初めてみよか#3~RDSとの接続~

はじめに

前回CodeStarのIDE環境(Cloud9)からコード編集Deployまではうまく行きました。
今度はいよいよ失敗していた(=環境が整っていなかった)DB接続を試します。

RDSの作成

RDSのコンソール画面の[データベースの作成]から作成します。
01.png

今回はMariaDBにしてみました。
02-1.png
もちろん無料枠でDBインスタンス名、マスターユーザ名、マスターパスワードを入力します。
解説には@も逝けると書いてありますが、パスワードを確認欄のVaridationで弾かれるので@は無理っす
02-2.png
ここはデフォルトのまま
02-3.png
接続をDefault VPCとしました。
02-4.png
で最後に[データベースの作成]を押せば5~10分位で出来上がります。
02-5.png

RDSとの接続設定

このままではDefault VPCにCodeStarで作成したphp-laravelインスタンスとRDBインスタンスの疎通が取れません。
なぜならDefault VPCにMariaDBポートに対する接続設定が入っていないからです。これを設定していきます。

必要な情報は
- 接続元サーバとなるインスタンスのプライベートIP
- 接続先サーバで利用するポート番号

まずはCodeStarで作成したphp-laravelインスタンスのプライベートIPを確認します。
EC2ダッシュボードに移動し、インスタンスを表示します。
そうするとCodeStarのプロジェクト名-WebAppというインスタンスがいますのでそのプライベートIPをコピーしNodepadなどに書き写しておきます。
01.png

ポート番号はMariaDB標準から変えていないので3306ですね。(RDSの作成時に替えた場合はその値に)

RDSがぶら下がっているVPCからアクセスしましょうか。
Amazon RDSのダッシュボードに移り、データベースメニューからDB識別子のリンクをクリックします。
02.png
03.png
ここに出てくるVPC-セキュリティグループのリンクをクリックします。
04.png
EC2ダッシュボードのネットワーク&セキュリティ>セキュリティーグループにフィルターがかかった状態で遷移します。
画面下部のインバウンドをクリックし、先ほどのプライベートIPとポート番号を指定します。
05.png

RDSとの接続確認

では、CodeStarのプロジェクトのインスタンスにTeraTermで接続します。
初期状態ではmysqlのモジュールも入っていないのでインストールします。
※PHP実行サーバーに入れたくない!ってセキュリティ意識の高い方は別の運用用EC2を立ち上げ操作を行ってください。
 その際は、上のRDSとの接続で指定したインバウンドの他に運用用EC2のプライベートIPも定義してあげます。

コマンドが入っていないことの確認(笑)
-hで指定するホスト名はRDSダッシュボードのデータベース識別子を選択した後の画面にでるエンドポイントのホスト名です。

[ec2-user@ip-172-31-29-127 phplaravel]$ mysql -u admin -p -h laravel-db.culgyynq9ap1.us-east-2.rds.amazonaws.com
-bash: mysql: command not found
[ec2-user@ip-172-31-29-127 phplaravel]$ sudo yum install -y mysql
Loaded plugins: priorities, update-motd, upgrade-helper
amzn-main                                                | 2.1 kB     00:00
amzn-updates                                             | 2.5 kB     00:00
Resolving Dependencies
--> Running transaction check
---> Package mysql.noarch 0:5.5-1.6.amzn1 will be installed
--> Processing Dependency: mysql55 >= 5.5 for package: mysql-5.5-1.6.amzn1.noarch
--> Running transaction check
---> Package mysql55.x86_64 0:5.5.62-1.23.amzn1 will be installed
--> Processing Dependency: real-mysql55-libs(x86-64) = 5.5.62-1.23.amzn1 for package: mysql55-5.5.62-1.23.amzn1.x86_64
--> Processing Dependency: mysql-config for package: mysql55-5.5.62-1.23.amzn1.x86_64
--> Running transaction check
---> Package mysql-config.x86_64 0:5.5.62-1.23.amzn1 will be installed
---> Package mysql55-libs.x86_64 0:5.5.62-1.23.amzn1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package           Arch        Version                  Repository         Size
================================================================================
Installing:
 mysql             noarch      5.5-1.6.amzn1            amzn-main         2.7 k
Installing for dependencies:
 mysql-config      x86_64      5.5.62-1.23.amzn1        amzn-updates       49 k
 mysql55           x86_64      5.5.62-1.23.amzn1        amzn-updates      7.5 M
 mysql55-libs      x86_64      5.5.62-1.23.amzn1        amzn-updates      816 k

Transaction Summary
================================================================================
Install  1 Package (+3 Dependent packages)

Total download size: 8.3 M
Installed size: 31 M
Downloading packages:
(1/4): mysql-5.5-1.6.amzn1.noarch.rpm                      | 2.7 kB   00:00
(2/4): mysql-config-5.5.62-1.23.amzn1.x86_64.rpm           |  49 kB   00:00
(3/4): mysql55-libs-5.5.62-1.23.amzn1.x86_64.rpm           | 816 kB   00:00
(4/4): mysql55-5.5.62-1.23.amzn1.x86_64.rpm                | 7.5 MB   00:00
--------------------------------------------------------------------------------
Total                                              9.9 MB/s | 8.3 MB  00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : mysql55-libs-5.5.62-1.23.amzn1.x86_64                        1/4
  Installing : mysql-config-5.5.62-1.23.amzn1.x86_64                        2/4
  Installing : mysql55-5.5.62-1.23.amzn1.x86_64                             3/4
  Installing : mysql-5.5-1.6.amzn1.noarch                                   4/4
  Verifying  : mysql-5.5-1.6.amzn1.noarch                                   1/4
  Verifying  : mysql-config-5.5.62-1.23.amzn1.x86_64                        2/4
  Verifying  : mysql55-libs-5.5.62-1.23.amzn1.x86_64                        3/4
  Verifying  : mysql55-5.5.62-1.23.amzn1.x86_64                             4/4

Installed:
  mysql.noarch 0:5.5-1.6.amzn1

Dependency Installed:
  mysql-config.x86_64 0:5.5.62-1.23.amzn1   mysql55.x86_64 0:5.5.62-1.23.amzn1
  mysql55-libs.x86_64 0:5.5.62-1.23.amzn1

Complete!
[ec2-user@ip-172-31-29-127 phplaravel]$ 

気を取り直してもう一度。今度はちゃんと接続できました。

[ec2-user@ip-172-31-29-127 phplaravel]$ mysql -u admin -p -h laravel-db.culgyynq9ap1.us-east-2.rds.amazonaws.com
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 234
Server version: 5.5.5-10.2.21-MariaDB-log Source distribution

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

これでCodeStarのインスタンスからRDSへの接続が可能となりました。

RDSにデータベースを作成する

ここはMariaDBのコマンドなので覚書です。

mysql> CREATE DATABASE laravel DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
Query OK, 1 row affected (0.00 sec)

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| innodb             |
| laravel            |
| mysql              |
| performance_schema |
+--------------------+
5 rows in set (0.00 sec)

mysql> \q
Bye

これでlaravel用のDBのガワができました。
本当はマスターユーザではなくLaravel用データベース接続ユーザを作るべきでしょうが
そこはスルーしました。

いよいよphp artisan migrate

ここまで来たらいよいよlaravelからDBをmigrateしてやります。
デフォルトの接続設定がローカルホストでしたのでまずは.envを書き換えます。

[ec2-user@ip-172-31-29-127 ~]$ cd /var/www/phplaravel/
[ec2-user@ip-172-31-29-127 phplaravel]$ cat .env
APP_ENV=local
APP_KEY=base64:CHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHA=
APP_DEBUG=false
APP_LOG_LEVEL=error
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

PUSHER_APP_ID=
PUSHER_KEY=
PUSHER_SECRET=
[ec2-user@ip-172-31-29-127 phplaravel]$

これを書き換えます。具体的には以下の様にします。

Env Value
DB_CONNECTION mysql
DB_HOST laravel-db.culgyynq9ap1.us-east-2.rds.amazonaws.com
DB_PORT 3306
DB_DATABASE laravel
DB_USERNAME admin
DB_PASSWORD ***********
[ec2-user@ip-172-31-29-127 phplaravel]$ vi .env
[ec2-user@ip-172-31-29-127 phplaravel]$ cat .env
APP_ENV=local
APP_KEY=base64:CHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHA=
APP_DEBUG=false
APP_LOG_LEVEL=error
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=laravel-db.culgyynq9ap1.us-east-2.rds.amazonaws.com
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=admin
DB_PASSWORD=***********

BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

PUSHER_APP_ID=
PUSHER_KEY=
PUSHER_SECRET=
[ec2-user@ip-172-31-29-127 phplaravel]$

ではお待ちかねのartisan migrate

[ec2-user@ip-172-31-29-127 phplaravel]$ php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
[ec2-user@ip-172-31-29-127 phplaravel]$

はい。これでWeb+DBの環境は整いました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Cloud Formation

AWS Cloud Formationとは?

AWS内のインフラをjsonやYAMLでプロビジョニングする機能。(Infrastructure as Code)

どのように実行されるのか

テンプレートを作成
(jsonかYAML形式のテキストファイル)

Cloud Formationが*スタックにまとめる

プロビジョニング

*スタックとは
テンプレートに宣言されたリソース

ユースケース

検証・開発・本番環境のように複数の環境が必要な際、テンプレート化することで展開を簡略化できる

料金

Cloud Formation自体に料金はかからない。
作成したリソースにのみ課金。

実行方法

  • マネジメントコンソールより実行 →テンプレートをアップロード、スタックに名前を付け、実行
  • CLIより実行 →aws cloudformation create-stack --stack-name SampleStack --template-body file://ファイル名.[json | yaml]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Cloud Front

Amazon Cloud Frontとは?

フルマネージドのCDNシステム。

CDNとは
「Content Delivery Network」の略。
オリジンサーバの負荷軽減のために、エッジサーバにキャッシュを保存させること。

AWSでは

  • S3やALB、EC2などがオリジンサーバとなり、Cloud Frontがエッジサーバとなる
  • AWS外で管理しているエンドポイントの指定も可能
  • フルマネージドなので、細かい設定を気にしなくて良い
  • AWS WAFで作成したウェブACLを適用することでwebアプリケーションファイアウォールとして設定できる
  • オリジンサーバの情報を入力するほかは、デフォルト設定のままでも機能する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nexus Helm Chartを複数のDockerレジストリに対応できるようにする

はじめに

Sonatype Nexusはmaven, gradle, npm, dockerなどに対応したプライベートリポジトリマネージャーです。
権限制御も充実しているのでこれを採用している企業も多いようです。

公式Helm ChartもあるのでKubernetes上に簡単にデプロイすることができます。
しかしながらデフォルトのChartではDockerレジストリを1つしか作成することができません。
そこで今回はNexus Helm Chartを改変して複数のDockerレジストリに対応できるようにします。

使用したコードはGitHubにあげてあります。

環境情報

macOS Mojave 10.14.1
Helm: 3.0.1
EKS: 1.14
Nexus Helm Chart: 1.22.0

アーキテクチャ構成

公式のNexus Helm Chartをそのままデプロイすると下図の構成になります。
nexus.sample.comはHTTPでNexusにアクセスする際のドメインでdocker.sample.comはDockerレジストリにアクセスする際のドメインです。
また、ALB Ingress Controllerは独自にデプロイしたものでありNexus Helm Chartには含まれません。

スクリーンショット 2020-02-09 16.41.37.png

Podの中にはNexusのコンテナに加えてNexus Proxyというコンテナが含まれます。
Nexus Proxyはその名の通りプロキシとして機能し、下記の環境変数を設定することでHTTPホストとDockerホストのルーティングを行います。

  • NEXUS_HTTP_HOST
  • UPSTREAM_HTTP_PORT
  • NEXUS_DOCKER_HOST
  • UPSTREAM_DOCKER_PORT

使用する環境変数が定められているのでDockerレジストリは1つに限られてしまうのが問題となります。
実際の業務においてはリリース用や開発用のレジストリで分けて誤リリースを避けたいというケースもあるかと思います。

今回は公式のHelm Chartを改修して下図のような構成にします。
至って普通の構成です。(そもそも何故公式ではプロキシコンテナ使ってるのか謎)

スクリーンショット 2020-02-09 18.24.48.png

Helm Chart改修

まずはNexus Helm Chartを取得します。templateを編集するためローカルに持って来る必要があります。

$ helm fetch stable/sonatype-nexus --version 1.22.0
$ tar -xvzf sonatype-nexus-1.22.0.tgz

ディレクトリ構成は以下になります。デプロイ時はvalues.yamlを使わず自分で作成したmy-values.yamlを使います。
*が付いているファイルを今回編集していきます。

.
├── Chart.yaml
├── README.md
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── backup-pv.yaml
│   ├── backup-pvc.yaml
│   ├── backup-secret.yaml
│   ├── configmap.yaml
│   ├── deployment-statefulset.yaml *
│   ├── ingress.yaml *
│   ├── proxy-ks-secret.yaml
│   ├── proxy-route.yaml
│   ├── proxy-svc.yaml *
│   ├── pv.yaml
│   ├── pvc.yaml
│   ├── route.yaml
│   ├── secret.yaml
│   ├── service.yaml *
│   └── serviceaccount.yaml
├── values.yaml
└── my-values.yaml *

values

まずはmy-values.yamlを作成します。

my-values.yaml
statefulset:
  enabled: true
nexus:
  livenessProbe:
    port: 8081
  readinessProbe:
    port: 8081
  hosts:
  - host: nexus.sample.com
    port: 8081
  - host: docker-dev.sample.com
    port: 5003
  - host: docker-release.sample.com
    port: 15003
nexusProxy:
  enabled: false
ingress:
  enabled: true
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
  tls:
    enabled: false
service:
  enabled: true
  serviceType: NodePort

nexus.livenessProbe.portnexus.readinessProbe.portnexus.hostsは自分で定義したkeyとなります。Ingress、Service、StatefulSetに使われます。
Ingress ControllerにはALB Ingress Controllerを使用します。これはデプロイされている前提として進めます。

template

続いてtemplateを編集していきます。

proxy-svc.yaml.Values.nexusProxy.enabled=falseにするだけでプロキシ用のServiceが作成されないようにします。

proxy-svc.yaml
- {{- if or .Values.nexusProxy.enabled .Values.ingress.enabled }}
+ {{- if .Values.nexusProxy.enabled }}

ingress.yamlは自分で定義した.Values.nexus.hostsが同一のServiceにルーティングされるように下記内容を追記します。

ingress.yaml
spec:
  rules:
+     {{- range .Values.nexus.hosts }}
+     - host: {{ .host }}
+       http:
+         paths:
+           - backend:
+               serviceName: {{ template "nexus.name" $ }}-service
+               servicePort: {{ .port}}
+     {{- end }}

service.yamlも同様に自分で定義した.Values.nexus.hostsの値が反映されるように編集します。

service.yaml
spec:
  ports:
-   {{- with .Values.service.ports  }}
- {{ toYaml . | indent 2 }}
-   {{- end }}
+   {{- range .Values.nexus.hosts }}
+   - name: port-{{ .port }}
+     port: {{ .port }}
+     targetPort: {{ .port }}
+   {{- end }}

deployment-statefulset.yamlでも同様です。既存のcontainerPortは不要なので取り除きます。

deployment-statefulset.yaml
spec:
  template:
    spec:
      containers:
        - name: nexus
          ports:
-             - containerPort: {{ .Values.nexus.dockerPort }}
-               name: nexus-docker-g
-             - containerPort: {{ .Values.nexus.nexusPort }}
-               name: nexus-http
+             {{- range .Values.nexus.hosts }}
+             - name: port-{{ .port }}
+               containerPort: {{ .port }}
+             {{- end }}
-
          livenessProbe:
            httpGet:
-               port: {{ .Values.nexus.nexusPort }}
+               port: {{ .Values.nexus.livenessProbe.port }}
          readinessProbe:
            httpGet:
-               port: {{ .Values.nexus.nexusPort }}
+               port: {{ .Values.nexus.readinessProbe.port }}

デプロイ

あとはhelmコマンドでデプロイするだけです。

$ helm install --values ./my-values.yaml sonatype-nexus ./

Ingressで定義したドメインについて名前解決できるようRoute53等で設定すればNexusにアクセスできるようになります。

スクリーンショット 2020-02-09 19.33.30.png

Dockerレジストリ作成

Dockerレジストリ作成はNexusの画面から行う必要があります。

adminユーザでログインし、Repositories設定画面からリポジトリ作成をします。
スクリーンショット 2020-02-09 19.14.35.png

Recipeにはdocker(hosted)を選択します。
スクリーンショット 2020-02-09 19.17.10.png

詳細設定はNameとHTTP Portだけ入力して他はデフォルトで問題ありません。
スクリーンショット 2020-02-09 19.20.29.png

「Create repository」を押せば完了です。Dockerレジストリが作成されていることが確認できます。
スクリーンショット 2020-02-09 19.23.51.png

開発時用のDockerレジストリを作成したので同様の手順でリリース用のものも作成します。

検証

実際に2つのDockerレジストリを検証します。
まずはローカルにDockerイメージを落とします。

$ docker pull alpine:3.9

$ docker images
REPOSITORY  TAG  IMAGE ID      CREATED      SIZE
alpine      3.9  82f67be598eb  2 weeks ago  5.53MB

タグ付けしてdocker pushします。
HTTPの場合はinsecure registryに登録することに注意しましょう。

$ docker tag 82f67be598eb docker-dev.sample.com/alpine:3.7
$ docker push docker-dev.sample.com/alpine:3.7

$ docker tag 82f67be598eb docker-release.sample.com/alpine:3.7
$ docker push docker-release.sample.com/alpine:3.7

画面から2種類のDockerレジストリにイメージが格納されていることが確認できます。
スクリーンショット 2020-02-09 20.31.04.png
スクリーンショット 2020-02-09 20.32.43.png

おわりに

公式のNexus Helm Chartを改修して複数Dockerレジストリに対応できることを確認しました。
Helm Chartのtemplateの編集箇所が多いとバージョンが上がったときに都度対応しなくてはならないのが面倒ですが、今回についてはしょうがないかなと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

macOSにAWS CLI をインストールする

概要

自身のMacにAWS CLIをインストールした時の備忘録。
公式ドキュメントを元に作業。

環境

  • macOS Catalina

前提条件

  • Python 2 バージョン 2.7 以降または Python 3 バージョン 3.4 以降

構築

Pythonのバージョン確認

$ python --version
Python 2.7.16

バージョン2.7以降のPythonがインストールされているから前提条件をクリアしているが、
なんとなく、Python3もインストールする。

Python3 インストール

# パッケージの検索
$ brew search python3
==> Formulae
boost-python3       python3             python@3            python@3.8

# Python3 インストール
$ brew install python3

# python3 インストール確認
$ python3 --version
Python 3.7.6

これで、Python3でインストールする準備が整ったので、次にAWS CLIをインストールする。

# AWS CLI ダウンロード
$ curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip"

# AWS CLI 解凍
$ unzip awscli-bundle.zip

# AWS CLI インストール (Pythonのバージョンを指定してインストール)
$ sudo /usr/local/bin/python3 awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws

# AWS CLI インストール確認
$ aws --version
aws-cli/1.17.13 Python/3.7.6 Darwin/19.3.0 botocore/1.14.13

Pythonのバージョンは、デフォルトバージョンで実行されてしまうため、デフォルトバージョンを変更するか、Pythonアプリケーションの絶対パスを指定してあげる必要がある。今回は「/usr/local/bin/python3」の部分で絶対パスを指定して実行している。

デフォルトのバージョンを変更したい場合は、pyenvでバージョン切り替えを行うと良さそう。

参考

参考:https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-macos.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kubernetes The Hard Way - AWS (日本語訳)

はじめに

CKA受験に向けてKubernetes The Hard Wayをやろうと思っていたのですが、慣れ親しんでいるのがAWSであったため、AWSで同じようなことが出来るものがないか探していました。

Github上にいくつか見つけたのですが、いかんせん英語に弱いため、都度都度Google翻訳で訳していました。

今後の学習のことを考えて、フォークして日本語訳を行いました。
フォーク元

よろしければ、ご使用ください。
また、間違っている点などありましたら、コメント頂ければ幸いです。

リンク

https://github.com/danonb10/kubernetes-the-hard-way-aws

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】作成済みのEC2インスタンスを複製する方法~AMIの作成~

はじめに

似たようなEC2インスタンスを何個も作る際にAMIを使うととても便利だったのでまとめておきます
これによってRubyやNginx、Node.jsなどのパッケージのインストール作業などを何度もやらなくて済み、数分でインスタンスを複製できるのでとても便利でした

前提

AWSで作成済みのEC2インスタンスがある

対象読者

EC2インスタンスを新規で作成したいけど、パッケージのインストールなど諸々の作業がめんどくさいと思っている人

AMIって何?

Amazon Machine Imageの略
インスタンスの作成に必要なソフトウェア構成 (OS、アプリケーションサーバー、アプリケーション) を含むテンプレートのこと

EC2インスタンスを作成する際に出てくる以下の画面で選択するのがAMIです
通常はここからAWSが提供しているAmazon Linux 2 AMIなどを選択してEC2インスタンスを作成しますが、
今回はこのAMIを自分で作ってそこからEC2インスタンスを作成するという流れになります
スクリーンショット 2020-02-09 18.33.55.png

作業の流れ

  1. 複製元となるEC2インスタンスからAMIイメージを作成する
  2. AMIイメージから新規のEC2インスタンスを作成する
  3. 作成したインスタンスにssh接続して、インスタンスの中身を確認する

1. 複製元となるEC2インスタンスからAMIイメージを作成する

まずは複製するインスタンスを決めて停止させます
*停止させなくてもできるのですが、AWS非推奨でデータの整合性が取れない場合があります

今回は停止したChat-spaceという名のインスタンスを複製することとします
*Elastic IPアドレスがEC2インスタンスに紐づいていないと課金されてしまいますので、必ずアドレスの関連付け解除と解放をしてから停止させましょう
1.png

複製するインスタンスを選択し、アクションからイメージの作成をクリックします
スクリーンショット 2020-02-09 18.47.33.png

次に実際のAMIの設定を入力していきます
イメージの説明はあとで変更可能ですが、イメージ名は変更できないので注意してください
今回はtest-serverとします
ボリュームタイプなども選べますが、今回はデフォルトの設定とします
イメージの作成を押すとAMIの作成が始まります
スクリーンショット 2020-02-09 18.47.49.png

左側のタブのイメージ>AMIをクリックすると、作成したAMIを確認できます
しばらくするとステータスがavailableになったAMIが作成されます
スクリーンショット 2020-02-09 18.47.57.png

2. AMIイメージから新規のEC2インスタンスを作成する

次に新規のEC2インスタンスを起動していきます
作成されたAMIを選択して、起動ボタンをクリックします
すると、EC2インスタンスを作成するいつもの画面が出てくるので設定をしていきます

インスタンスタイプ

今回は無料枠のtc2.microとします
スクリーンショット 2020-02-09 18.47.00.png

インスタンスの設定

ネットワークの設定などを行います
自分で作成したVPCやサブネットがあればここで選択します
今回はデフォルト値のままとします
自動割り当てパブリックIPは有効になってないとインターネット通信ができないので注意してください
スクリーンショット 2020-02-09 18.48.14.png

ストレージの追加

ストレージを追加できます
今回はデフォルトのままとします
スクリーンショット 2020-02-09 18.48.22.png

タグの追加

必須ではないですが、タグ付けをしておくと何のインスタンスなのか一目でわかるのでつけておくと便利です
Nameキーで好きなインスタンス名をつけておくことが一般的です
今回はtest-serverとします
スクリーンショット 2020-02-09 18.48.30.png

セキュリティグループの設定

既存のセキュリティグループがある場合はそれを選択することもできますので適宜設定してください
今回は新規作成することとします
ルールの追加を押し、HTTP, HTTPS, ポート3000をフルオープンで許可します
スクリーンショット 2020-02-09 18.48.41.png

最後にキーペアを選択してください
今回は既存のdeploytestというキーペアを使います
インスタンスの作成を押すと作成が始まります
スクリーンショット 2020-02-09 18.55.20.png

3. 作成したインスタンスにssh接続して、インスタンスの中身を確認する

EC2インスタンスが作成されたらパッケージがインストールされているか確認していきます

その前に今回はパッケージがインストールされていないとどういう表示になるかを先に見てみます
nginxやnode.jsがインストールされてるかを確認しましたが、下記のように何も出力されません
3.png

ではいよいよ作成したインスタンスにssh接続します
先ほど設定したキーペアを使ってください
Elastic IPアドレスは必要であれば適宜設定してください
なければインスタンスのパブリックIPアドレスを@以降に記述して接続します

インスタンスに接続した状態でnginxやnode.jsがインストールされているかを確認すると、下記のようにインストールされていることが確認できました
4.png

このようにAMIを使うと、あるインスタンスに対して行った設定を引き継いで複製することができます

ちなみに今回はchat-spaceというアプリケーションを入れたインスタンスだったので下記のようにそのアプリケーションディレクトリまで複製されてしまっています

今回はこのアプリケーションは使わないので下記コマンドで削除しています
これでchat-spaceのディレクトリは消去されました
あとは好きなアプリをgit cloneするなりしてください
スクリーンショット 2020-02-09 17.11.34.png

まとめ

パッケージのインストール作業はめんどくさいことが多いですが、1つ作っておけば今回の方法で複製できるのでとても便利です
作成時間も10分くらいでできちゃいますので、同じようなインスタンスを作成する場合はぜひ使ってみてください

間違いなどあれば指摘していただけると嬉しいです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Lexで簡単なボットを作成してみた

はじめに

この記事はLexの勉強用として試したことをまとめたものです。
書籍「AWSでつくる-AIプログラミング入門」を参考にして対話ボットを作成してみました。

Amazon Lexについて

  • 対話型インターフェイスを作成することができるAIサービスである。
  • 会話ボットを簡単に構築することができる。
  • Amazon Alexaで使われている深層学習技術と同じ技術を使用している。
  • 現時点で日本語未対応である。

IAMロールを作成

ボットを作成

インテントを作成

スロットタイプの作成

ボットを構築

  • 「Build」ボタンを押下して、ボットを作成する。
  • 以下のように設定してみた。 sampleボット.png

Lambdaを作成

  • 以下スクリプトを作成する。
import json
import datetime
import time
import os
import dateutil.parser
import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

# --- Main ---
def lambda_handler(event, context):
    print(event)
    return dispatch(event)

# --- Input Check ---
def try_ex(func):
    try:
        return func()
    except KeyError:
        return None

# --- Intent Check ---
def dispatch(intent_request):

    logger.debug('dispatch userId={}, intentName={}'.format(intent_request['userId'], intent_request['currentIntent']['name']))

    intent_name = intent_request['currentIntent']['name']

    if intent_name == 'OrderIntent':
        return ice_cream_order(intent_request)

# --- Intent Request ---
def ice_cream_order(intent_request):

    Container = try_ex(lambda: intent_request['currentIntent']['slots']['Container'])
    Flavor = try_ex(lambda: intent_request['currentIntent']['slots']['Flavor'])
    session_attributes = intent_request['sessionAttributes'] if intent_request['sessionAttributes'] is not None else {}
    print(session_attributes)

    if intent_request['invocationSource'] == 'DialogCodeHook':
        # Validate any slots which have been specified.  If any are invalid, re-elicit for their value
        validation_result = validate_order(intent_request['currentIntent']['slots'])
        if not validation_result['isValid']:
            slots = intent_request['currentIntent']['slots']
            slots[validation_result['violatedSlot']] = None

            return elicit_slot(
                session_attributes,
                intent_request['currentIntent']['name'],
                slots,
                validation_result['violatedSlot'],
                validation_result['message']
            )

    print(Container)
    print(Flavor)
    msg = "OK, {} ice cream in {}".format(Flavor, Container)

    print("json : " + str(msg))
    return close(
        session_attributes,
        'Fulfilled',
        {
            'contentType': 'PlainText',
            'content': msg
        }
    )
  • Lexへ返すための関数を定義する。
    • ElicitSlot・・・ユーザーがレスポンスでスロット値を提供するよう予期されていることをLexに伝える。
    • Close・・・ユーザーからのレスポンスを予期しないようにLexに伝える。レスポンス不要である。
# --- Validate Check ---
def validate_order(slots):
    Container = try_ex(lambda: slots['Container'])
    Flavor = try_ex(lambda: slots['Flavor'])

    if Container is None:
        return build_validation_result(
            False,
            'Container',
            'corn or cup ?'
        )

    if Flavor is None:
        return build_validation_result(
            False,
            'Flavor',
            'vanilla, chocolate or strawberry ?'
        )

    return {'isValid': True}

# --- Validate Result ---
def build_validation_result(isvalid, violated_slot, message_content):
    return {
        'isValid': isvalid,
        'violatedSlot': violated_slot,
        'message': {'contentType': 'PlainText', 'content': message_content}
    }

# --- Responses Build ---
def elicit_slot(session_attributes, intent_name, slots, slot_to_elicit, message):
    return {
        'sessionAttributes': session_attributes,
        'dialogAction': {
            'type': 'ElicitSlot',
            'intentName': intent_name,
            'slots': slots,
            'slotToElicit': slot_to_elicit,
            'message': message
        }
    }

# --- Responses Close ---
def close(session_attributes, fulfillment_state, message):
    response = {
        'sessionAttributes': session_attributes,
        'dialogAction': {
            'type': 'Close',
            'fulfillmentState': fulfillment_state,
            'message': message
        }
    }
    print("Res : " + str(response))
    return response

会話のテスト

  • コンソール画面からテストができるので試してみる。
  • 「ice cream order」とLexを呼び出してみると会話が始まる。
    sample会話_OK.png

  • 登録されていないインテントでLexを呼び出してみると「Please try again.」と返ってくる。
    (Error handlingでインテントに入らなかった時にどう返すか定義しているため。)
    sample会話_NG.png

まとめ

  • Lexへ返すための形式に苦労したが一度作成してしまえば色々と応用できそう。
  • 日本語対応が待ち遠しい。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CDKプロジェクトのセットアップ

CDKでなにか作り始める手順のメモ。

プロジェクトディレクトリの初期化

まずは、アプリケーションの雛形の準備。

$ mkdir my-cdk-proj
$ cd my-cdk-proj
$ npx cdk init app --language=typescript
npx: 235個のパッケージを25.117秒でインストールしました。
Applying project template app for typescript
Initializing a new git repository...
Executing npm install...
npm WARN deprecated left-pad@1.3.0: use String.prototype.padStart()
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN my-cdk-proj@0.1.0 No repository field.
npm WARN my-cdk-proj@0.1.0 No license field.

# Welcome to your CDK TypeScript project!

This is a blank project for TypeScript development with CDK.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

 * `npm run build`   compile typescript to js
 * `npm run watch`   watch for changes and compile
 * `npm run test`    perform the jest unit tests
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template

yarnを使うなら次のコマンドを実行

$ yarn
$ rm package-lock.json

この時点で次のコマンドを実行して、古くなっているライブラリはアップグレードしておく。

$ yarn outdated
$ yarn upgrade

アップグレードしたら一旦、コミットしておく。

ESLint の設定

サクッと、モジュールを追加して eslint --init を実行する。
色々質問される。

$ yarn add -D eslint
$ eslint --init
? How would you like to use ESLint? 
  To check syntax only 
  To check syntax and find problems 
❯ To check syntax, find problems, and enforce code style
? What type of modules does your project use? (Use arrow keys)
❯ JavaScript modules (import/export) 
  CommonJS (require/exports) 
  None of these 
? Which framework does your project use? 
  React 
  Vue.js 
❯ None of these
? Does your project use TypeScript? (y/N) y
? Where does your code run? 
 ◯ Browser
❯◉ Node
? How would you like to define a style for your project? (Use arrow keys)
❯ Use a popular style guide 
  Answer questions about your style 
  Inspect your JavaScript file(s) 
? Which style guide do you want to follow? (Use arrow keys)
❯ Airbnb: https://github.com/airbnb/javascript 
  Standard: https://github.com/standard/standard 
  Google: https://github.com/google/eslint-config-google 
? What format do you want your config file to be in? 
  JavaScript 
❯ YAML 
  JSON 
Checking peerDependencies of eslint-config-airbnb-base@latest
The config that you've selected requires the following dependencies:

@typescript-eslint/eslint-plugin@latest eslint-config-airbnb-base@latest eslint@^5.16.0 || ^6.1.0 eslint-plugin-import@^2.18.2 @typescript-eslint/parser@latest
? Would you like to install them now with npm? (Y/n) y
Installing @typescript-eslint/eslint-plugin@latest, eslint-config-airbnb-base@latest, eslint@^5.16.0 || ^6.1.0, eslint-plugin-import@^2.18.2, @typescript-eslint/parser@latest

+ eslint-plugin-import@2.20.1
+ eslint@6.8.0
+ eslint-config-airbnb-base@14.0.0
+ @typescript-eslint/parser@2.19.0
+ @typescript-eslint/eslint-plugin@2.19.0
added 100 packages from 59 contributors, removed 48 packages, updated 624 packages and audited 1204909 packages in 50.603s
found 0 vulnerabilities

Successfully created .eslintrc.yml file in /Users/morishita.hiromitsu/Develop/iko-yo/cypress-e2e-test-ci

最後にnpmを使ってインストールしてくれるので、yarn を使っている場合には、次を実行する。

$ yarn
$ rm package-lock.json

ここで最後の質問? Would you like to install them now with npm? (Y/n)No と答えると、package.json も更新されないまま終わってしまう。二度手間感はあるが、一旦 npm でインストールして、package.json を更新したほうが手間は少ないと思う。

で、終わったら一旦コミットしておく。

追加モジュールのインストール

yarn add -D eslint-import-resolver-typescript eslint-plugin-jest

.eslint.yaml の編集

とりあえずこれくらい設定しておく。後は必要に応じて足していく。

env:
  es6: true
  node: true
  jest/globals: true
extends:
  - airbnb-base
  - plugin:import/errors
  - plugin:import/warnings
  - plugin:import/typescript
globals:
  Atomics: readonly
  SharedArrayBuffer: readonly
parser: '@typescript-eslint/parser'
parserOptions:
  ecmaVersion: 2018
  sourceType: module
plugins:
  - '@typescript-eslint'
  - jest
  - import
ignorePatterns:
  - '*.js'
  - '*.d.ts'
rules:
  "@typescript-eslint/no-unused-vars": error
  import/extensions: [error, ignorePackages, { "ts": "never", "js": "never" }]
  import/no-unresolved: error
  import/prefer-default-export: off
  no-new: off
  no-useless-constructor: off

ここまで設定したら、次のコマンドで lint エラーを修正しておく。

$ eslint . --exrt .ts --fix

npm script の追加

package.json の scripts に次を足しておく

{
// 〜略〜
  "scripts": {
    // 〜略〜
    "eslint": "./node_modules/.bin/eslint . --ext .ts",
    "clean": "rm -rf ./{bin,lib,test}/{**,}/{*.d.ts,*.js}",
    // 〜略〜
  },
// 〜略〜
}

.editorconfig の追加

こんな感じで。

# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

# The JSON files contain newlines inconsistently
[*.json]
insert_final_newline = ignore

[*.md]
trim_trailing_whitespace = false

.envrc の作成

AWSのクレデンシャルなどリポジトリに入れられないものを環境変数で設定できるようにしておく。
最初にやっておく。
コードにとりあえずと思って書いてしまうと、必ずコミットしてプッシュすると思え!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Corretto 11で構築するLogstash

はじめに

日頃、AWSで構築したシステムやAWSサービスのログをElasticsearchに取り込んで監視や分析をしています。

Elasticsearchへのログ取り込み方法は、多くのやり方がありますが
自分はLogstashが好きなので使い続けています。

Logstash 6.7以降では、Amazon Linux2に対応しています。
また、同様に6.7以降では、OpenJDK11に対応しています。

そこで、Amazon Linux2でOpenJDK11を使ってLogstashを構築する手順を備忘録的に書きました。

【参考】
Product and Operating System
Logstash and JVM

利用環境

product version
logstash 7.5.2
Java 11.0.6
OS(EC2) Amazon Linux2 (t3.small)
AMI ID ami-011facbea5ec0363b
Region ap-noutheast-1

※投稿時点における最新版を採用しています。

前提として

OpenJDK11には、AWSが開発した独自OpenJDKとして
JavaSE標準と互換認定されているAmazon Corretto 11を採用しています。

また、LogstashはElasticラインセス版ではなく、Apache2.0ライセンス版を採用しています。

その理由は、次期メジャーバージョンの8系では
OSS版ElasticsearchにはLogstashもOSS版でないとデータを投入できなくなるからです。

現行バージョン7系のElasticライセンス版Logstashを使って、Amazon ESにデータ投入しようとすると
以下のようなエラーがLogstashのlogstash-plain.logに出力されます。

logstash-plain.log
[2020-02-04T03:42:38,051][WARN ][logstash.outputs.elasticsearch][XXX] DEPRECATION WARNING: Connecting to an OSS distribution of Elasticsearch using the default distribution of Logstash will stop working in Logstash 8.0.0. Please upgrade to the default distribution of Elasticsearch, or use the OSS distribution of Logstash {:url=>"https:// search-test-hoge.ap-northeast-1.es.amazonaws.com:443/"}

【参考】
Amazon Corretto
logstash-oss download

実施手順

  • 以下の手順で検証を実施します。

 1. OpenJDK11のインストール
 2. OSS版Logstashのインストール
 3. 追加プラグインのインストール
 4. サービス登録とサービス起動

1. OpenJDK11のインストール

  • yumコマンドでAmazon Corretto 11をインストールします。
AmazonCorretto11_Install
$ sudo yum install java-11-amazon-corretto
  • インストールが完了するとデフォルトでインストールしたOpenJDK11がセットされます。
Javaバージョン確認
$ java -version
openjdk version "11.0.6" 2020-01-14 LTS
OpenJDK Runtime Environment Corretto-11.0.6.10.1 (build 11.0.6+10-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.6.10.1 (build 11.0.6+10-LTS, mixed mode)

2. OSS版Logstashのインストール

image.png

  • wgetコマンドでコピーしたリンク先からRPMパッケージをダウンロードします。
  • ダウンロードしたRPMパッケージをrpmコマンドでインストールします。
logstash-oss_install
$ sudo wget https://artifacts.elastic.co/downloads/logstash/logstash-oss-7.5.2.rpm
$ sudo rpm -ivh logstash-oss-7.5.2.rpm
  • インストール完了後、yum list installedコマンドで確認します。
logstashインストール確認
$ yum list installed | grep logstash
logstash-oss.noarch                   1:7.5.2-1                      installed  

3. 追加プラグインのインストール

  • logstashの追加プラグインをインストールしてみます。(今回は、logstash-input-cloudwatch_logsを入れます)
plugin_install
$ sudo /usr/share/logstash/bin/logstash-plugin install logstash-input-cloudwatch_logs
Validating logstash-input-cloudwatch_logs
Installing logstash-input-cloudwatch_logs
Installation successful
  • logstash-plugin listコマンドで導入したプラグイン名をしていします。表示されればOKです。
pluginインストール確認
$ sudo /usr/share/logstash/bin/logstash-plugin list 'logstash-input-cloudwatch_logs'
logstash-input-cloudwatch_logs
  • 以下のような警告メッセージが表示されます。Java9以上を利用する場合に警告されます。無視しても動作には影響は出ません。
警告メッセージ
OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.headius.backport9.modules.Modules to method java.lang.Object.finalize()
WARNING: Please consider reporting this to the maintainers of com.headius.backport9.modules.Modules
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

4. サービス登録とサービス起動

  • systemctl enableコマンドでlogstashをサービス登録します。
サービス登録
$ sudo systemctl daemon-reload
$ sudo systemctl enable logstash
Created symlink from /etc/systemd/system/multi-user.target.wants/logstash.service to /etc/systemd/system/logstash.service.
  • systemctl start logstashコマンドで起動します。
Logstash起動
$ sudo systemctl start logstash
  • systemctl status logstashコマンドの結果、active (running)となっていればOKです。
Logtashステータス確認
$ sudo systemctl status logstash
● logstash.service - logstash
   Loaded: loaded (/etc/systemd/system/logstash.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2020-02-09 07:24:51 UTC; 4s ago
 Main PID: 32207 (java)
   CGroup: /system.slice/logstash.service
           └─32207 /bin/java -Xms1g -Xmx1g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djruby.compile.invokedynamic=true -Djruby.jit.threshold=0 -Djruby.regexp.interruptible=...

Feb 09 07:24:51 ip-172-31-93-30.ec2.internal systemd[1]: Started logstash.
Feb 09 07:24:51 ip-172-31-93-30.ec2.internal systemd[1]: Starting logstash...
Feb 09 07:24:51 ip-172-31-93-30.ec2.internal logstash[32207]: OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: An illegal reflective access operation has occurred
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: Illegal reflective access by com.headius.backport9.modules.Modules (file:/usr/share/logstash/logstash-core/lib/jars/jruby-complete-9.2.8.0.jar) to field java.io.FileDescriptor.fd
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: Please consider reporting this to the maintainers of com.headius.backport9.modules.Modules
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: All illegal access operations will be denied in a future release

まとめ

Amazon Corretto 11でもちゃんとLogstashが起動しましたね。
最近は、本記事の方法でLogstashを構築し、特に問題なく順調に利用出来ています^^v

これからAWS環境でLogstashを構築する場合は、ぜひ参考にしてみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS環境で構築するLogstashについて

はじめに

日頃、AWSで構築したシステムやAWSサービスのログをElasticsearchに取り込んで監視や分析をしています。

Elasticsearchへのログ取り込み方法は、多くのやり方がありますが
自分はLogstashが好きなので使い続けています。

Logstash 6.7以降では、Amazon Linux2に対応しています。
また、同様に6.7以降では、OpenJDK11に対応しています。

そこで、Amazon Linux2でOpenJDK11を使ってLogstashを構築する手順を備忘録的に書きました。

【参考】
Product and Operating System
Logstash and JVM

利用環境

product version
logstash 7.5.2
Java 11.0.6
OS(EC2) Amazon Linux2 (t3.small)
AMI ID ami-011facbea5ec0363b
Region ap-noutheast-1

※投稿時点における最新版を採用しています。

前提として

OpenJDK11には、AWSが開発した独自OpenJDKとして
JavaSE標準と互換認定されているAmazon Corretto 11を採用しています。

また、LogstashはElasticラインセス版ではなく、Apache2.0ライセンス版を採用しています。

その理由は、次期メジャーバージョンの8系では
OSS版ElasticsearchにはLogstashもOSS版でないとデータを投入できなくなるからです。

現行バージョン7系のElasticライセンス版Logstashを使って、Amazon ESにデータ投入しようとすると
以下のようなエラーがLogstashのlogstash-plain.logに出力されます。

logstash-plain.log
[2020-02-04T03:42:38,051][WARN ][logstash.outputs.elasticsearch][XXX] DEPRECATION WARNING: Connecting to an OSS distribution of Elasticsearch using the default distribution of Logstash will stop working in Logstash 8.0.0. Please upgrade to the default distribution of Elasticsearch, or use the OSS distribution of Logstash {:url=>"https:// search-test-hoge.ap-northeast-1.es.amazonaws.com:443/"}

【参考】
Amazon Corretto
logstash-oss download

実施手順

  • 以下の手順で検証を実施します。

 1. OpenJDK11のインストール
 2. OSS版Logstashのインストール
 3. 追加プラグインのインストール
 4. サービス登録とサービス起動

1. OpenJDK11のインストール

  • yumコマンドでAmazon Corretto 11をインストールします。
AmazonCorretto11_Install
$ sudo yum install java-11-amazon-corretto
  • インストールが完了するとデフォルトでインストールしたOpenJDK11がセットされます。
Javaバージョン確認
$ java -version
openjdk version "11.0.6" 2020-01-14 LTS
OpenJDK Runtime Environment Corretto-11.0.6.10.1 (build 11.0.6+10-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.6.10.1 (build 11.0.6+10-LTS, mixed mode)

2. OSS版Logstashのインストール

image.png

  • wgetコマンドでコピーしたリンク先からRPMパッケージをダウンロードします。
  • ダウンロードしたRPMパッケージをrpmコマンドでインストールします。
logstash-oss_install
$ sudo wget https://artifacts.elastic.co/downloads/logstash/logstash-oss-7.5.2.rpm
$ sudo rpm -ivh logstash-oss-7.5.2.rpm
  • インストール完了後、yum list installedコマンドで確認します。
logstashインストール確認
$ yum list installed | grep logstash
logstash-oss.noarch                   1:7.5.2-1                      installed  

3. 追加プラグインのインストール

  • logstashの追加プラグインをインストールしてみます。(今回は、logstash-input-cloudwatch_logsを入れます)
plugin_install
$ sudo /usr/share/logstash/bin/logstash-plugin install logstash-input-cloudwatch_logs
Validating logstash-input-cloudwatch_logs
Installing logstash-input-cloudwatch_logs
Installation successful
  • logstash-plugin listコマンドで導入したプラグイン名をしていします。表示されればOKです。
pluginインストール確認
$ sudo /usr/share/logstash/bin/logstash-plugin list 'logstash-input-cloudwatch_logs'
logstash-input-cloudwatch_logs
  • 以下のような警告メッセージが表示されます。Java9以上を利用する場合に警告されます。無視しても動作には影響は出ません。
警告メッセージ
OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.headius.backport9.modules.Modules to method java.lang.Object.finalize()
WARNING: Please consider reporting this to the maintainers of com.headius.backport9.modules.Modules
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

4. サービス登録とサービス起動

  • systemctl enableコマンドでlogstashをサービス登録します。
サービス登録
$ sudo systemctl daemon-reload
$ sudo systemctl enable logstash
Created symlink from /etc/systemd/system/multi-user.target.wants/logstash.service to /etc/systemd/system/logstash.service.
  • systemctl start logstashコマンドで起動します。
Logstash起動
$ sudo systemctl start logstash
  • systemctl status logstashコマンドの結果、active (running)となっていればOKです。
Logtashステータス確認
$ sudo systemctl status logstash
● logstash.service - logstash
   Loaded: loaded (/etc/systemd/system/logstash.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2020-02-09 07:24:51 UTC; 4s ago
 Main PID: 32207 (java)
   CGroup: /system.slice/logstash.service
           └─32207 /bin/java -Xms1g -Xmx1g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djruby.compile.invokedynamic=true -Djruby.jit.threshold=0 -Djruby.regexp.interruptible=...

Feb 09 07:24:51 ip-172-31-93-30.ec2.internal systemd[1]: Started logstash.
Feb 09 07:24:51 ip-172-31-93-30.ec2.internal systemd[1]: Starting logstash...
Feb 09 07:24:51 ip-172-31-93-30.ec2.internal logstash[32207]: OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: An illegal reflective access operation has occurred
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: Illegal reflective access by com.headius.backport9.modules.Modules (file:/usr/share/logstash/logstash-core/lib/jars/jruby-complete-9.2.8.0.jar) to field java.io.FileDescriptor.fd
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: Please consider reporting this to the maintainers of com.headius.backport9.modules.Modules
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
Feb 09 07:24:53 ip-172-31-93-30.ec2.internal logstash[32207]: WARNING: All illegal access operations will be denied in a future release

まとめ

Amazon Corretto 11でもちゃんとLogstashが起動しましたね。
最近は、本記事の方法でLogstashを構築し、特に問題なく順調に利用出来ています^^v

これからAWS環境でLogstashを構築する場合は、ぜひ参考にしてみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS勉強会のアウトプット

今回やったこと

Macの操作について学んだことが合った

  • 2本指の状態でパットを押すと「右クリックが開く」と同じ役割を果たしてくれる。

SSHフォルダにキーペアファイルをいれる

  • SSHフォルダは隠しフォルダとして隠れている状態。隠しファイルを解除すれば閲覧することができる。
  • 参考サイト:隠しファイル・フォルダを表示する方法
  • そもそもフォルダ自体なかったので新規でフォルダを作成。その中にキーペアのファイルを突っ込んだ。

キーペアの仕組みがイマイチ理解できなかった

  • そもそもキーペアってなんぞや?
  • ダウンロードフォルダにキーペアファイルがない状態であれば成功。

インスタンスの仕組みもイマイチ理解できなかった

  • チュートリアルに沿って作成してみたが明らかに初心者向けではなかった。

使用しないインスタンスは止めないと課金される可能性がある

  • 無料版で作成すれば課金されることはないが念のため止めといた方が良い。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

serverless framworkでLambda+APIGatewayをdeployする

はじめに

アプリケーションを Lambda にデプロイする際どんな方法を使用しているだろうか?
AWS SUM ,code deploy と使用してきたがコマンドを複数回叩かないといけなかったりとちょっと面倒臭かった。
そこで最近自分の周りで話題になっている serverless framwork を試してみる。
何やらコマンド一つでビルド・デプロイを行ってくれたりとかなり便利らしい。
スクリーンショット 2020-02-09 11.34.10.png

Serverless Framework インストール

以下のコマンドを実行し Serverless Framework をインストールする。

$ npm install -g serverless

ローカル端末で Serverless Framework を利用する場合は別途 Credentials の設定が必要になる。

サービス作成

Serverless Framework のサービスを作成する。
今回はcreate-stock-price-indexというサービス名で、Python 3.6の Lambda ファンクションを作成する。

$ serverless create --template aws-python3 --path create-stock-price-index

create-stock-price-indexディレクトリ配下に以下のファイルが作成される。

  • serverless.yml
  • handler.py

今回の deploy ではhandler.pyこちらで作成したコードapp.pyに置き換える。
あわせて、serverless.ymlも以下のように変更しておく。

serverless.yml
service: create-stock-price-index

provider:
  name: aws
  runtime: python3.7
  region: ap-northeast-1

functions:
  get-policy-number-list:
    handler: app.lambda_handler

外部モジュールのインストール

いざ deploy と行きたいところだが、app.pyは以下のように標準で同梱されている以外の外部モジュールを使用している。

app.py
import requests
from bs4 import BeautifulSoup
import yaml

この外部モジュールの管理について今回は UnitedIncome/serverless-python-requirements いう Serverless Framework のプラグインを使用する。
まず、プラグインをインストールする。
Serverless Framework で管理しているディレクトリ上で以下のコマンドを実行する。

$ npm install --save serverless-python-requirements

続いて、serverless.ymlに plugins を追加する。
plugins プロパティで利用するプラグインを指定する。

serverless.yml
service: create-stock-price-index

provider:
  name: aws
  runtime: python3.7
  region: ap-northeast-1

plugins:
  - serverless-python-requirements

functions:
  get-policy-number-list:
    handler: app.lambda_handler

最後に、requirements.txtを用意する。
requirements.txtには import する外部モジュールとその依存関係を記載する。

requirements.txt
beautifulsoup4==4.8.2
certifi==2019.11.28
chardet==3.0.4
idna==2.8
PyYAML==5.3
requests==2.22.0
soupsieve==1.9.5
urllib3==1.25.7

デプロイ

前項まででデプロイの準備が整ったのでいよいよデプロイを実行していく。
create-stock-price-indexディレクトリに移動し、デプロイコマンドを実行する。

$ sls deploy -v

ここで pip と setuptools のバージョンが古いとエラーが出たのでアップデートする。
すると、 CloudFormation により必要なリソースを作成し、 Lambda ファンクションがデプロイされた事が確認できる。
スクリーンショット 2020-02-04 22.33.00.png

デプロイした Lambda を実行してみる。


$ sls invoke -f get-policy-number-list
{
    "投資指標": [
        {
            "証券コード": 3242,
            "予想PER": "7.77倍",
            "予想EPS": "46.2",
            "実績PBR": "1.08倍",
            "実績BPS": "332.15",
            "予想配当利": "6.69%"
        },
        {
            "証券コード": 9904,
            "予想PER": "15.88倍",
            "予想EPS": "22.1",
            "実績PBR": "1.79倍",
            "実績BPS": "196.51",
            "予想配当利": "5.68%"
        }
    ]
}

問題なく実行でき、期待通りの結果を得る事ができた。

API Gateway 作成

API Gateway を構築することでエンドポイントに対する Get リクエストをトリガーに Lambda を実行することができる。
serverless.ymlに Lambda ファンクションを呼び出すためのイベントを定義する。

serverless.yml
service: create-stock-price-index

provider:
  name: aws
  runtime: python3.7
  region: ap-northeast-1

plugins:
  - serverless-python-requirements

functions:
  get-policy-number-list:
    handler: app.lambda_handler
    events:
      - http:
          path: /
          method: get
          integration: lambda
          request:
            template:
              application/json: '{ "user_id" : "$input.params("password")" }'

デプロイコマンドを実行する。

api keys:
  None
endpoints:
  GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
functions:
  get-policy-number-list: create-stock-price-index-dev-get-policy-number-list
layers:
  None

デプロイが完了すると ServiceEndpoint が出力される。
このエンドポイントに対してGetリクエストを送ってみる。

Unicodeが文字化けするのでちょっと細工する。

$ curl -X GET "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/"  | perl -Xpne 's/\\u([0-9a-fA-F]{4})/chr(hex($1))/eg'
{"投資指標": [{"証券コード": 3242, "予想PER": "7.81倍", "予想EPS": "46.2", "実績PBR": "1.09倍", "実績BPS": "332.15", "予想配当利": "6.65%"}, {"証券コード": 9904, "予想PER": "16.02倍", "予想EPS": "22.1", "実績PBR": "1.8倍", "実績BPS": "196.51", "予想配当利": "5.63%"}]}

期待値通りの結果が出力された。

リソースの削除

一通り確認が終わったらAWS上のリソースを削除しておく。

$ sls remove -v

おわりに

簡単なインストールと設定で準備を整える事ができ、その上コマンド一つでデプロイ、スタックの削除と行えるなはかなり便利だった。
パッケージ管理についてもプラグインをインストールするだけでrequirements.txtがそのまま使用できたのも好印象だった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

そろそろaws初めてみよか#2~CodeStarによるコード修正からDeployまで~

はじめに

そろそろaws初めてみよか~まずは触ってみた~で紹介したようにawsの海をもがいている訳ですが(笑)

CodeStarの環境が整ったので試しにコードを編集しCommitしてDeployまで流れるか確認
その後、DBのmigrateをしてみようと思います。
結果からお伝えすると、
- コードCommitからDeployまではOK
- DBのmigrateはNG
でした。

コード編集からDeployまで

Cloud9(IDE)を利用してコードを修正、それをGit CommitしてDeployまでの流れを確認しました。

CodeStarからプロジェクトを選択し、Cloud9を起動します。
暫くするとGitからPullしてきますので確認します。

今回は簡単にWelcomePageのタイトルを修正します。
修正と言っても「!!」を後ろにつけただけという(笑)

01.png

AWSのGitにCommitします。

ec2-user:~/environment $ cd php-laravel/
ec2-user:~/environment/php-laravel (master) $ git add .
ec2-user:~/environment/php-laravel (master) $ git commit -m 'change Title'
[master 42bc467] change Title
 Committer: EC2 Default User <ec2-user@ip-172-31-20-112.us-east-2.compute.internal>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global user.name "Your Name"
    git config --global user.email you@example.com

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author

 1 file changed, 1 insertion(+), 1 deletion(-)
ec2-user:~/environment/php-laravel (master) $ git push origin master
Counting objects: 5, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 435 bytes | 435.00 KiB/s, done.
Total 5 (delta 4), reused 0 (delta 0)
To https://git-codecommit.us-east-2.amazonaws.com/v1/repos/php-laravel
   a0c0157..42bc467  master -> master
ec2-user:~/environment/php-laravel (master) $ 

すると勝手にDeployまで走っている様子がパイプラインの画面から確認できます。

02.png

実際のアプリケーションを確認するにはCodeStarダッシュボードの「アプリケーションのエンドポイント」をクリック

03.png

見辛いですが、タイトルが変わってます(笑)

04.png

こんな感じで開発していくのは分かるのですが…
本番環境とローカル環境のずれを前回確認しているのでどうやって実際に使っていくのかは
まだイメージできていません(^^;

DBとの接続

環境の確認

CodeStarによりプロジェクトを作成すると実行するインスタンスとCloud9(IDE)用のインスタンスが生成されます。
興味があるのは実行するインスタンス側なのでEC2ダッシュボード画面からインスタンス「php-laravel-WebApp」の「パブリック DNS (IPv4)」にTeraTermで接続します。
※ちなみに前回の記事ではプロジェクト名を「laravel」としてましたが、何度か削除に失敗して「php-laravel」としています。
 関連するリソースが綺麗に消えてくれなくて困っています(笑)


       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
[ec2-user@ip-172-31-29-127 ~]$ cd /var/www/phplaravel/
[ec2-user@ip-172-31-29-127 phplaravel]$ ls
app        composer.json  database     package.json  resources   storage
artisan    composer.lock  gulpfile.js  phpunit.xml   routes      tests
bootstrap  config         index.php    public        server.php  vendor
[ec2-user@ip-172-31-29-127 phplaravel]$ php artisan migrate


  [Illuminate\Database\QueryException]
  could not find driver (SQL: select * from information_schema.tables where t
  able_schema = homestead and table_name = migrations)



  [PDOException]
  could not find driver

えっと...could not find driverって


       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
[ec2-user@ip-172-31-29-127 ~]$ cd /var/www/phplaravel/
[ec2-user@ip-172-31-29-127 phplaravel]$ ls -alF
total 220
drwxr-xr-x 12 ec2-user ec2-user   4096 Feb  9 00:01 ./
drwxr-xr-x  8 root     root       4096 Feb  8 19:11 ../
drwxr-xr-x  6 ec2-user ec2-user   4096 Feb  9 00:01 app/
-rw-r--r--  1 ec2-user ec2-user   1646 Feb  9 00:01 artisan
drwxr-xr-x  3 ec2-user ec2-user   4096 Feb  9 00:01 bootstrap/
-rw-r--r--  1 ec2-user ec2-user   1283 Feb  9 00:01 composer.json
-rw-r--r--  1 ec2-user ec2-user 130327 Feb  9 00:00 composer.lock
drwxr-xr-x  2 ec2-user ec2-user   4096 Feb  9 00:01 config/
drwxr-xr-x  5 ec2-user ec2-user   4096 Feb  9 00:01 database/
-rw-r--r--  1 ec2-user ec2-user    543 Feb  9 00:01 .env
-rw-r--r--  1 ec2-user ec2-user    543 Feb  9 00:00 .env.example
-rw-r--r--  1 ec2-user ec2-user     61 Feb  9 00:01 .gitattributes
-rw-r--r--  1 ec2-user ec2-user     80 Feb  9 00:01 .gitignore
-rw-r--r--  1 ec2-user ec2-user    558 Feb  9 00:01 gulpfile.js
-rw-r--r--  1 ec2-user ec2-user   1776 Feb  9 00:01 index.php
-rw-r--r--  1 ec2-user ec2-user    390 Feb  9 00:00 package.json
-rw-r--r--  1 ec2-user ec2-user    930 Feb  9 00:01 phpunit.xml
drwxr-xr-x  5 ec2-user ec2-user   4096 Feb  9 00:01 public/
drwxr-xr-x  5 ec2-user ec2-user   4096 Feb  9 00:01 resources/
drwxr-xr-x  2 ec2-user ec2-user   4096 Feb  9 00:01 routes/
-rw-r--r--  1 ec2-user ec2-user    563 Feb  9 00:01 server.php
drwxrwxr-x  5 ec2-user apache     4096 Feb  9 00:01 storage/
drwxr-xr-x  2 ec2-user ec2-user   4096 Feb  9 00:01 tests/
drwxr-xr-x 32 ec2-user ec2-user   4096 Feb  9 00:01 vendor/
[ec2-user@ip-172-31-29-127 phplaravel]$ more .env
APP_ENV=local
APP_KEY=base64:CHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHA=
APP_DEBUG=false
APP_LOG_LEVEL=error
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

PUSHER_APP_ID=
PUSHER_KEY=
PUSHER_SECRET=
[ec2-user@ip-172-31-29-127 phplaravel]$

「.env」を覗くとローカルホストの「mysql」って書いてあります(汗
でもmysql入ってないし…


       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
[ec2-user@ip-172-31-29-127 ~]$ rpm -qa | grep mysql
[ec2-user@ip-172-31-29-127 ~]$ 

そんな訳で追加インストール

無いのならインストールするしかないのでまずはPDOのmysqlをインストールしました。
$ sudo yum install -y php73-mysqlnd

するとPDOの例外が変わりましたね。
[PDOException]
SQLSTATE[HY000] [2002] Connection refused

DB_DATABASE、DB_USERNAME、DB_PASSWORDが違うので、これで正解です(笑)

[ec2-user@ip-172-31-29-127 phplaravel]$ sudo yum list *mysql*
Loaded plugins: priorities, update-motd, upgrade-helper
Available Packages
MySQL-python26.x86_64                          1.2.3-11.14.amzn1    amzn-main
MySQL-python27.x86_64                          1.2.3-11.14.amzn1    amzn-main
apr-util-mysql.x86_64                          1.5.4-6.18.amzn1     amzn-main
collectd-mysql.x86_64                          5.8.0-2.19.amzn1     amzn-main
dovecot-mysql.x86_64                           1:2.2.10-5.17.amzn1  amzn-main
exim-mysql.x86_64                              4.92-1.25.amzn1      amzn-updates
freeradius-mysql.x86_64                        2.2.6-7.16.amzn1     amzn-main
libdbi-dbd-mysql.x86_64                        0.8.3-5.1.5.amzn1    amzn-main
lighttpd-mod_authn_mysql.x86_64                1.4.53-1.36.amzn1    amzn-updates
lighttpd-mod_mysql_vhost.x86_64                1.4.53-1.36.amzn1    amzn-updates
mod_auth_mysql.x86_64                          1:3.0.0-18.10.amzn1  amzn-main
mysql.noarch                                   5.5-1.6.amzn1        amzn-main
mysql-bench.noarch                             5.5-1.6.amzn1        amzn-main
mysql-common.noarch                            5.5-1.6.amzn1        amzn-main
mysql-config.x86_64                            5.5.62-1.23.amzn1    amzn-updates
mysql-connector-java.noarch                    1:5.1.12-2.10.amzn1  amzn-main
mysql-connector-odbc.x86_64                    5.1.11-1.12.amzn1    amzn-main
mysql-devel.noarch                             5.5-1.6.amzn1        amzn-main
mysql-embedded.noarch                          5.5-1.6.amzn1        amzn-main
mysql-embedded-devel.noarch                    5.5-1.6.amzn1        amzn-main
mysql-libs.noarch                              5.5-1.6.amzn1        amzn-main
mysql-server.noarch                            5.5-1.6.amzn1        amzn-main
mysql-test.noarch                              5.5-1.6.amzn1        amzn-main
mysql51.x86_64                                 5.1.73-8.72.amzn1    amzn-main
mysql51-bench.x86_64                           5.1.73-8.72.amzn1    amzn-main
mysql51-common.x86_64                          5.1.73-8.72.amzn1    amzn-main
mysql51-devel.x86_64                           5.1.73-8.72.amzn1    amzn-main
mysql51-embedded.x86_64                        5.1.73-8.72.amzn1    amzn-main
mysql51-embedded-devel.x86_64                  5.1.73-8.72.amzn1    amzn-main
mysql51-libs.i686                              5.1.73-8.72.amzn1    amzn-main
mysql51-libs.x86_64                            5.1.73-8.72.amzn1    amzn-main
mysql51-server.x86_64                          5.1.73-8.72.amzn1    amzn-main
mysql51-test.x86_64                            5.1.73-8.72.amzn1    amzn-main
mysql55.x86_64                                 5.5.62-1.23.amzn1    amzn-updates
mysql55-bench.x86_64                           5.5.62-1.23.amzn1    amzn-updates
mysql55-devel.x86_64                           5.5.62-1.23.amzn1    amzn-updates
mysql55-embedded.x86_64                        5.5.62-1.23.amzn1    amzn-updates
mysql55-embedded-devel.x86_64                  5.5.62-1.23.amzn1    amzn-updates
mysql55-libs.i686                              5.5.62-1.23.amzn1    amzn-updates
mysql55-libs.x86_64                            5.5.62-1.23.amzn1    amzn-updates
mysql55-server.x86_64                          5.5.62-1.23.amzn1    amzn-updates
mysql55-test.x86_64                            5.5.62-1.23.amzn1    amzn-updates
mysql56.x86_64                                 5.6.46-1.35.amzn1    amzn-updates
mysql56-bench.x86_64                           5.6.46-1.35.amzn1    amzn-updates
mysql56-common.i686                            5.6.46-1.35.amzn1    amzn-updates
mysql56-common.x86_64                          5.6.46-1.35.amzn1    amzn-updates
mysql56-devel.x86_64                           5.6.46-1.35.amzn1    amzn-updates
mysql56-embedded.x86_64                        5.6.46-1.35.amzn1    amzn-updates
mysql56-embedded-devel.x86_64                  5.6.46-1.35.amzn1    amzn-updates
mysql56-errmsg.x86_64                          5.6.46-1.35.amzn1    amzn-updates
mysql56-libs.i686                              5.6.46-1.35.amzn1    amzn-updates
mysql56-libs.x86_64                            5.6.46-1.35.amzn1    amzn-updates
mysql56-server.x86_64                          5.6.46-1.35.amzn1    amzn-updates
mysql56-test.x86_64                            5.6.46-1.35.amzn1    amzn-updates
mysql57.x86_64                                 5.7.28-1.14.amzn1    amzn-updates
mysql57-common.i686                            5.7.28-1.14.amzn1    amzn-updates
mysql57-common.x86_64                          5.7.28-1.14.amzn1    amzn-updates
mysql57-devel.x86_64                           5.7.28-1.14.amzn1    amzn-updates
mysql57-embedded.x86_64                        5.7.28-1.14.amzn1    amzn-updates
mysql57-embedded-devel.x86_64                  5.7.28-1.14.amzn1    amzn-updates
mysql57-errmsg.x86_64                          5.7.28-1.14.amzn1    amzn-updates
mysql57-libs.i686                              5.7.28-1.14.amzn1    amzn-updates
mysql57-libs.x86_64                            5.7.28-1.14.amzn1    amzn-updates
mysql57-server.x86_64                          5.7.28-1.14.amzn1    amzn-updates
mysql57-test.x86_64                            5.7.28-1.14.amzn1    amzn-updates
nagios-plugins-mysql.x86_64                    1.4.16-5.8.amzn1     amzn-main
perl-DBD-MySQL.x86_64                          4.023-5.17.amzn1     amzn-main
perl-DBD-MySQL55.x86_64                        4.023-5.23.amzn1     amzn-main
perl-DBD-MySQL56.x86_64                        4.023-5.21.amzn1     amzn-main
perl-DateTime-Format-MySQL.noarch              0.04-18.2.amzn1      amzn-main
perl-Time-Piece-MySQL.noarch                   0.05-20.2.amzn1      amzn-main
php-ZendFramework-Db-Adapter-Mysqli.noarch     1.12.20-1.12.amzn1   amzn-main
php-ZendFramework-Db-Adapter-Pdo-Mysql.noarch  1.12.20-1.12.amzn1   amzn-main
php-mysql.x86_64                               5.3.29-1.8.amzn1     amzn-main
php-mysqlnd.x86_64                             5.3.29-1.8.amzn1     amzn-main
php54-mysql.x86_64                             5.4.45-1.75.amzn1    amzn-main
php54-mysqlnd.x86_64                           5.4.45-1.75.amzn1    amzn-main
php55-mysqlnd.x86_64                           5.5.38-2.119.amzn1   amzn-main
php56-mysqlnd.x86_64                           5.6.40-1.143.amzn1   amzn-updates
php70-mysqlnd.x86_64                           7.0.33-1.32.amzn1    amzn-updates
php71-mysqlnd.x86_64                           7.1.33-1.43.amzn1    amzn-updates
php72-mysqlnd.x86_64                           7.2.26-1.19.amzn1    amzn-updates
php73-mysqlnd.x86_64                           7.3.13-1.22.amzn1    amzn-updates
rsyslog-mysql.x86_64                           5.8.10-9.26.amzn1    amzn-main
ruby-mysql.x86_64                              2.8.2-1.11.amzn1     amzn-main

[ec2-user@ip-172-31-29-127 phplaravel]$ sudo yum install -y php73-mysqlnd
Loaded plugins: priorities, update-motd, upgrade-helper
Resolving Dependencies
--> Running transaction check
---> Package php73-mysqlnd.x86_64 0:7.3.13-1.22.amzn1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package            Arch        Version                 Repository         Size
================================================================================
Installing:
 php73-mysqlnd      x86_64      7.3.13-1.22.amzn1       amzn-updates      336 k

Transaction Summary
================================================================================
Install  1 Package

Total download size: 336 k
Installed size: 812 k
Downloading packages:
php73-mysqlnd-7.3.13-1.22.amzn1.x86_64.rpm                 | 336 kB   00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : php73-mysqlnd-7.3.13-1.22.amzn1.x86_64                       1/1
  Verifying  : php73-mysqlnd-7.3.13-1.22.amzn1.x86_64                       1/1

Installed:
  php73-mysqlnd.x86_64 0:7.3.13-1.22.amzn1

Complete!

[ec2-user@ip-172-31-29-127 phplaravel]$ php artisan migrate


  [Illuminate\Database\QueryException]
  SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_s
  chema.tables where table_schema = homestead and table_name = migrations)



  [PDOException]
  SQLSTATE[HY000] [2002] Connection refused


[ec2-user@ip-172-31-29-127 phplaravel]$

RDS(DBサーバ)と接続する準備は整ったのでまたキャプチャー取り始めます(笑)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

そろそろaws初めてみよか#2

はじめに

そろそろaws初めてみよかで紹介したようにawsの海をもがいている訳ですが(笑)

CodeStarの環境が整ったので試しにコードを編集しCommitしてDeployまで流れるか確認
その後、DBのmigrateをしてみようと思います。
結果からお伝えすると、
- コードCommitからDeployまではOK
- DBのmigrateはNG
でした。

コード編集からDeployまで

Cloud9(IDE)を利用してコードを修正、それをGit CommitしてDeployまでの流れを確認しました。

CodeStarからプロジェクトを選択し、Cloud9を起動します。
暫くするとGitからPullしてきますので確認します。

今回は簡単にWelcomePageのタイトルを修正します。
修正と言っても「!!」を後ろにつけただけという(笑)

01.png

AWSのGitにCommitします。

ec2-user:~/environment $ cd php-laravel/
ec2-user:~/environment/php-laravel (master) $ git add .
ec2-user:~/environment/php-laravel (master) $ git commit -m 'change Title'
[master 42bc467] change Title
 Committer: EC2 Default User <ec2-user@ip-172-31-20-112.us-east-2.compute.internal>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global user.name "Your Name"
    git config --global user.email you@example.com

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author

 1 file changed, 1 insertion(+), 1 deletion(-)
ec2-user:~/environment/php-laravel (master) $ git push origin master
Counting objects: 5, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 435 bytes | 435.00 KiB/s, done.
Total 5 (delta 4), reused 0 (delta 0)
To https://git-codecommit.us-east-2.amazonaws.com/v1/repos/php-laravel
   a0c0157..42bc467  master -> master
ec2-user:~/environment/php-laravel (master) $ 

すると勝手にDeployまで走っている様子がパイプラインの画面から確認できます。

02.png

実際のアプリケーションを確認するにはCodeStarダッシュボードの「アプリケーションのエンドポイント」をクリック

03.png

見辛いですが、タイトルが変わってます(笑)

04.png

こんな感じで開発していくのは分かるのですが…
本番環境とローカル環境のずれを前回確認しているのでどうやって実際に使っていくのかは
まだイメージできていません(^^;

DBとの接続

環境の確認

CodeStarによりプロジェクトを作成すると実行するインスタンスとCloud9(IDE)用のインスタンスが生成されます。
興味があるのは実行するインスタンス側なのでEC2ダッシュボード画面からインスタンス「php-laravel-WebApp」の「パブリック DNS (IPv4)」にTeraTermで接続します。
※ちなみに前回の記事ではプロジェクト名を「laravel」としてましたが、何度か削除に失敗して「php-laravel」としています。
 関連するリソースが綺麗に消えてくれなくて困っています(笑)


       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
[ec2-user@ip-172-31-29-127 ~]$ cd /var/www/phplaravel/
[ec2-user@ip-172-31-29-127 phplaravel]$ ls
app        composer.json  database     package.json  resources   storage
artisan    composer.lock  gulpfile.js  phpunit.xml   routes      tests
bootstrap  config         index.php    public        server.php  vendor
[ec2-user@ip-172-31-29-127 phplaravel]$ php artisan migrate


  [Illuminate\Database\QueryException]
  could not find driver (SQL: select * from information_schema.tables where t
  able_schema = homestead and table_name = migrations)



  [PDOException]
  could not find driver

えっと...could not find driverって


       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
[ec2-user@ip-172-31-29-127 ~]$ cd /var/www/phplaravel/
[ec2-user@ip-172-31-29-127 phplaravel]$ ls -alF
total 220
drwxr-xr-x 12 ec2-user ec2-user   4096 Feb  9 00:01 ./
drwxr-xr-x  8 root     root       4096 Feb  8 19:11 ../
drwxr-xr-x  6 ec2-user ec2-user   4096 Feb  9 00:01 app/
-rw-r--r--  1 ec2-user ec2-user   1646 Feb  9 00:01 artisan
drwxr-xr-x  3 ec2-user ec2-user   4096 Feb  9 00:01 bootstrap/
-rw-r--r--  1 ec2-user ec2-user   1283 Feb  9 00:01 composer.json
-rw-r--r--  1 ec2-user ec2-user 130327 Feb  9 00:00 composer.lock
drwxr-xr-x  2 ec2-user ec2-user   4096 Feb  9 00:01 config/
drwxr-xr-x  5 ec2-user ec2-user   4096 Feb  9 00:01 database/
-rw-r--r--  1 ec2-user ec2-user    543 Feb  9 00:01 .env
-rw-r--r--  1 ec2-user ec2-user    543 Feb  9 00:00 .env.example
-rw-r--r--  1 ec2-user ec2-user     61 Feb  9 00:01 .gitattributes
-rw-r--r--  1 ec2-user ec2-user     80 Feb  9 00:01 .gitignore
-rw-r--r--  1 ec2-user ec2-user    558 Feb  9 00:01 gulpfile.js
-rw-r--r--  1 ec2-user ec2-user   1776 Feb  9 00:01 index.php
-rw-r--r--  1 ec2-user ec2-user    390 Feb  9 00:00 package.json
-rw-r--r--  1 ec2-user ec2-user    930 Feb  9 00:01 phpunit.xml
drwxr-xr-x  5 ec2-user ec2-user   4096 Feb  9 00:01 public/
drwxr-xr-x  5 ec2-user ec2-user   4096 Feb  9 00:01 resources/
drwxr-xr-x  2 ec2-user ec2-user   4096 Feb  9 00:01 routes/
-rw-r--r--  1 ec2-user ec2-user    563 Feb  9 00:01 server.php
drwxrwxr-x  5 ec2-user apache     4096 Feb  9 00:01 storage/
drwxr-xr-x  2 ec2-user ec2-user   4096 Feb  9 00:01 tests/
drwxr-xr-x 32 ec2-user ec2-user   4096 Feb  9 00:01 vendor/
[ec2-user@ip-172-31-29-127 phplaravel]$ more .env
APP_ENV=local
APP_KEY=base64:CHANGEMECHANGEMECHANGEMECHANGEMECHANGEMECHA=
APP_DEBUG=false
APP_LOG_LEVEL=error
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

PUSHER_APP_ID=
PUSHER_KEY=
PUSHER_SECRET=
[ec2-user@ip-172-31-29-127 phplaravel]$

「.env」を覗くとローカルホストの「mysql」って書いてあります(汗
でもmysql入ってないし…


       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
[ec2-user@ip-172-31-29-127 ~]$ rpm -qa | grep mysql
[ec2-user@ip-172-31-29-127 ~]$ 

そんな訳で追加インストール

無いのならインストールするしかないのでまずはPDOのmysqlをインストールしました。
$ sudo yum install -y php73-mysqlnd

するとPDOの例外が変わりましたね。
[PDOException]
SQLSTATE[HY000] [2002] Connection refused

DB_DATABASE、DB_USERNAME、DB_PASSWORDが違うので、これで正解です(笑)

[ec2-user@ip-172-31-29-127 phplaravel]$ sudo yum list *mysql*
Loaded plugins: priorities, update-motd, upgrade-helper
Available Packages
MySQL-python26.x86_64                          1.2.3-11.14.amzn1    amzn-main
MySQL-python27.x86_64                          1.2.3-11.14.amzn1    amzn-main
apr-util-mysql.x86_64                          1.5.4-6.18.amzn1     amzn-main
collectd-mysql.x86_64                          5.8.0-2.19.amzn1     amzn-main
dovecot-mysql.x86_64                           1:2.2.10-5.17.amzn1  amzn-main
exim-mysql.x86_64                              4.92-1.25.amzn1      amzn-updates
freeradius-mysql.x86_64                        2.2.6-7.16.amzn1     amzn-main
libdbi-dbd-mysql.x86_64                        0.8.3-5.1.5.amzn1    amzn-main
lighttpd-mod_authn_mysql.x86_64                1.4.53-1.36.amzn1    amzn-updates
lighttpd-mod_mysql_vhost.x86_64                1.4.53-1.36.amzn1    amzn-updates
mod_auth_mysql.x86_64                          1:3.0.0-18.10.amzn1  amzn-main
mysql.noarch                                   5.5-1.6.amzn1        amzn-main
mysql-bench.noarch                             5.5-1.6.amzn1        amzn-main
mysql-common.noarch                            5.5-1.6.amzn1        amzn-main
mysql-config.x86_64                            5.5.62-1.23.amzn1    amzn-updates
mysql-connector-java.noarch                    1:5.1.12-2.10.amzn1  amzn-main
mysql-connector-odbc.x86_64                    5.1.11-1.12.amzn1    amzn-main
mysql-devel.noarch                             5.5-1.6.amzn1        amzn-main
mysql-embedded.noarch                          5.5-1.6.amzn1        amzn-main
mysql-embedded-devel.noarch                    5.5-1.6.amzn1        amzn-main
mysql-libs.noarch                              5.5-1.6.amzn1        amzn-main
mysql-server.noarch                            5.5-1.6.amzn1        amzn-main
mysql-test.noarch                              5.5-1.6.amzn1        amzn-main
mysql51.x86_64                                 5.1.73-8.72.amzn1    amzn-main
mysql51-bench.x86_64                           5.1.73-8.72.amzn1    amzn-main
mysql51-common.x86_64                          5.1.73-8.72.amzn1    amzn-main
mysql51-devel.x86_64                           5.1.73-8.72.amzn1    amzn-main
mysql51-embedded.x86_64                        5.1.73-8.72.amzn1    amzn-main
mysql51-embedded-devel.x86_64                  5.1.73-8.72.amzn1    amzn-main
mysql51-libs.i686                              5.1.73-8.72.amzn1    amzn-main
mysql51-libs.x86_64                            5.1.73-8.72.amzn1    amzn-main
mysql51-server.x86_64                          5.1.73-8.72.amzn1    amzn-main
mysql51-test.x86_64                            5.1.73-8.72.amzn1    amzn-main
mysql55.x86_64                                 5.5.62-1.23.amzn1    amzn-updates
mysql55-bench.x86_64                           5.5.62-1.23.amzn1    amzn-updates
mysql55-devel.x86_64                           5.5.62-1.23.amzn1    amzn-updates
mysql55-embedded.x86_64                        5.5.62-1.23.amzn1    amzn-updates
mysql55-embedded-devel.x86_64                  5.5.62-1.23.amzn1    amzn-updates
mysql55-libs.i686                              5.5.62-1.23.amzn1    amzn-updates
mysql55-libs.x86_64                            5.5.62-1.23.amzn1    amzn-updates
mysql55-server.x86_64                          5.5.62-1.23.amzn1    amzn-updates
mysql55-test.x86_64                            5.5.62-1.23.amzn1    amzn-updates
mysql56.x86_64                                 5.6.46-1.35.amzn1    amzn-updates
mysql56-bench.x86_64                           5.6.46-1.35.amzn1    amzn-updates
mysql56-common.i686                            5.6.46-1.35.amzn1    amzn-updates
mysql56-common.x86_64                          5.6.46-1.35.amzn1    amzn-updates
mysql56-devel.x86_64                           5.6.46-1.35.amzn1    amzn-updates
mysql56-embedded.x86_64                        5.6.46-1.35.amzn1    amzn-updates
mysql56-embedded-devel.x86_64                  5.6.46-1.35.amzn1    amzn-updates
mysql56-errmsg.x86_64                          5.6.46-1.35.amzn1    amzn-updates
mysql56-libs.i686                              5.6.46-1.35.amzn1    amzn-updates
mysql56-libs.x86_64                            5.6.46-1.35.amzn1    amzn-updates
mysql56-server.x86_64                          5.6.46-1.35.amzn1    amzn-updates
mysql56-test.x86_64                            5.6.46-1.35.amzn1    amzn-updates
mysql57.x86_64                                 5.7.28-1.14.amzn1    amzn-updates
mysql57-common.i686                            5.7.28-1.14.amzn1    amzn-updates
mysql57-common.x86_64                          5.7.28-1.14.amzn1    amzn-updates
mysql57-devel.x86_64                           5.7.28-1.14.amzn1    amzn-updates
mysql57-embedded.x86_64                        5.7.28-1.14.amzn1    amzn-updates
mysql57-embedded-devel.x86_64                  5.7.28-1.14.amzn1    amzn-updates
mysql57-errmsg.x86_64                          5.7.28-1.14.amzn1    amzn-updates
mysql57-libs.i686                              5.7.28-1.14.amzn1    amzn-updates
mysql57-libs.x86_64                            5.7.28-1.14.amzn1    amzn-updates
mysql57-server.x86_64                          5.7.28-1.14.amzn1    amzn-updates
mysql57-test.x86_64                            5.7.28-1.14.amzn1    amzn-updates
nagios-plugins-mysql.x86_64                    1.4.16-5.8.amzn1     amzn-main
perl-DBD-MySQL.x86_64                          4.023-5.17.amzn1     amzn-main
perl-DBD-MySQL55.x86_64                        4.023-5.23.amzn1     amzn-main
perl-DBD-MySQL56.x86_64                        4.023-5.21.amzn1     amzn-main
perl-DateTime-Format-MySQL.noarch              0.04-18.2.amzn1      amzn-main
perl-Time-Piece-MySQL.noarch                   0.05-20.2.amzn1      amzn-main
php-ZendFramework-Db-Adapter-Mysqli.noarch     1.12.20-1.12.amzn1   amzn-main
php-ZendFramework-Db-Adapter-Pdo-Mysql.noarch  1.12.20-1.12.amzn1   amzn-main
php-mysql.x86_64                               5.3.29-1.8.amzn1     amzn-main
php-mysqlnd.x86_64                             5.3.29-1.8.amzn1     amzn-main
php54-mysql.x86_64                             5.4.45-1.75.amzn1    amzn-main
php54-mysqlnd.x86_64                           5.4.45-1.75.amzn1    amzn-main
php55-mysqlnd.x86_64                           5.5.38-2.119.amzn1   amzn-main
php56-mysqlnd.x86_64                           5.6.40-1.143.amzn1   amzn-updates
php70-mysqlnd.x86_64                           7.0.33-1.32.amzn1    amzn-updates
php71-mysqlnd.x86_64                           7.1.33-1.43.amzn1    amzn-updates
php72-mysqlnd.x86_64                           7.2.26-1.19.amzn1    amzn-updates
php73-mysqlnd.x86_64                           7.3.13-1.22.amzn1    amzn-updates
rsyslog-mysql.x86_64                           5.8.10-9.26.amzn1    amzn-main
ruby-mysql.x86_64                              2.8.2-1.11.amzn1     amzn-main

[ec2-user@ip-172-31-29-127 phplaravel]$ sudo yum install -y php73-mysqlnd
Loaded plugins: priorities, update-motd, upgrade-helper
Resolving Dependencies
--> Running transaction check
---> Package php73-mysqlnd.x86_64 0:7.3.13-1.22.amzn1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package            Arch        Version                 Repository         Size
================================================================================
Installing:
 php73-mysqlnd      x86_64      7.3.13-1.22.amzn1       amzn-updates      336 k

Transaction Summary
================================================================================
Install  1 Package

Total download size: 336 k
Installed size: 812 k
Downloading packages:
php73-mysqlnd-7.3.13-1.22.amzn1.x86_64.rpm                 | 336 kB   00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : php73-mysqlnd-7.3.13-1.22.amzn1.x86_64                       1/1
  Verifying  : php73-mysqlnd-7.3.13-1.22.amzn1.x86_64                       1/1

Installed:
  php73-mysqlnd.x86_64 0:7.3.13-1.22.amzn1

Complete!

[ec2-user@ip-172-31-29-127 phplaravel]$ php artisan migrate


  [Illuminate\Database\QueryException]
  SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_s
  chema.tables where table_schema = homestead and table_name = migrations)



  [PDOException]
  SQLSTATE[HY000] [2002] Connection refused


[ec2-user@ip-172-31-29-127 phplaravel]$

RDS(DBサーバ)と接続する準備は整ったのでまたキャプチャー取り始めます(笑)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Graphql React AWS backet

GraphQLを通してreactからAWSに画像保存

参考にしたURL
https://medium.com/@enespalaz/file-upload-with-graphql-9a4927775ef7
正直, これコピペすれば 90% は完成する.
しないといけないのは,AWS のバケットの設定 以下それについて

AWS S3

  • バケットを作る
  • アクセス権限のCORSの設定
  • アクセストークンの発行 //ここまでAWSのアカウント内でやる事
  • config.json の作成
  • https://hack-le.com/s3-everyone-read/

config.json

バケットに対して, バックエンドからアップロードしたりするのに必要

AWS.config.loadFromPath('./config.json');

中身は

{ 
  "accessKeyId": "ABCDEFGDHDHDHJDJHDJDJ",
  "secretAccessKey": "jfoaisdjfaosjfasjfasodjfasfjasdif",
  "region": "ap-northeast-1"
}

config.jsonと"region"は以下参照
config.json: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-json-file.html
region: https://docs.aws.amazon.com/general/latest/gr/rande.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む