2020年9月23日水曜日

aws-cli の ~/.aws/config でエンドポイントを切り替えるプラグイン


下記のaws-cliプラグインを使用すると、プロファイル上のサービスごとにエンドポイントを設定することができるらしい。

localstack」や「minio」を使用するときに便利かもしれない。


下記は、 ~/.aws/config にエンドポイントを設定した例です。

[profile local]
dynamodb =
    endpoint_url = http://localhost:8000





2020年9月16日水曜日

CloudFormationでEC2のSecurityGroupを変更すると置換が発生する

 

下記のようにEC2のNetworkInterfacesでSecurityGroupを設定していると、SecurityGroupを別のIDに変更したときにインスタンスの置換が発生します。

  Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      ImageId: !Ref Ec2AMI
      NetworkInterfaces:
        - DeviceIndex: "0"
          #NetworkInterfaceId: !Ref Eni00
          #AssociatePublicIpAddress: true
          GroupSet:
            - !GetAtt SecurityGroup01.GroupId
          SubnetId: {"Fn::ImportValue": !Sub "${StackNameVpc}-SubnetPublic1-Id"}
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}


変更セットを作って確認すると下図のとおり。「置換」がTrueです。


下記のようにAWS::EC2::NetworkInterfaceを使用すると、SecurityGroupを変更しても置換は発生しない。

  Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      ImageId: !Ref Ec2AMI
      NetworkInterfaces:
        - DeviceIndex: "0"
          NetworkInterfaceId: !Ref Eni00
          #AssociatePublicIpAddress: true
          #GroupSet:
          #  - !GetAtt SecurityGroup01.GroupId
          #SubnetId: {"Fn::ImportValue": !Sub "${StackNameVpc}-SubnetPublic1-Id"}
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}

  Eni00:
    Type: AWS::EC2::NetworkInterface
    Properties:
      GroupSet:
      - !GetAtt SecurityGroup01.GroupId
      SubnetId: {"Fn::ImportValue": !Sub "${StackNameVpc}-SubnetPublic1-Id"}



2020年9月13日日曜日

ipv6対応のCloudFront+S3をCloudFormationで作る

 
ipv6対応のCloudFront+S3をCloudFormationで作ってみました。
検証環境の構成イメージは下図のとおり。


[MEMO]

  • CloudFrontは、下記の独自ドメインでアクセスできるようにします。
    • https://blue21.tk・・・ipv4/ipv6対応
    • https://www.blue21.tk・・・ipv4のみ
  • CloudFrontは、特定のIPアドレスのみリクエストを許可するようにします。(WAFでアクセス制限)
  • S3には、静的コンテンツを格納し、CloudFrontからのリクエストのみ許可します。(OAIでアクセス制限)
  • ACMとWAFは、CloudFront用なので us-east-1 に作成します。(AWSの制約)
  • ACMで証明書を作成し、Route53でDNS検証します。なお、証明書はCloudFrontに登録します。
  • Route53のNameServerをドメイン・プロバイダに登録します。


環境構築


## 作業環境(ローカル環境)

構築作業は、WSL2(ubuntu20.04)で実施します。
なお、AWSのリージョンは us-east-1を使用します。
  • Windows10
  • WSL2(ubuntu20.04)
  • docker 19.03.12
  • make 4.2.1
  • aws-cli 1.17.14

1. aws-cli の設定


aws-cli実行時はプロファイルを指定します。プロファイルの設定は下記のとおり。
  • blue21 という名称でプロファイルを作成
  • リージョンは us-east-1 を使用し, Output形式は json
  • 管理者権限のアクセスキーを使用

2. ソース一式のdownload


環境構築に必要なCloudFormationテンプレートなどは、Bitbucketに登録しています。
Bitbucketの下記URLからダウンロードできます。

レポジトリの内容は以下のとおり。
.
├── README.md
├── bin
│   ├── Makefile
│   ├── README.md
│   ├── cfn_validate.sh
│   └── cfn_wait.sh
├── cfn
│   ├── Makefile
│   ├── README.md
│   ├── cfn_cf.yml
│   └── cfn_r53.yml
├── htdocs
│   ├── index.html
│   └── sorry.html
└── img
    ├── image01.drawio
    └── image01.png

3.独自ドメインの取得


検証用に無料の独自ドメイン(blue21.tk)を取得して、Route53 にパブリックのホストゾーンを作成しました。(参考にした手順は、下記URLを参照)
Route53のホストゾーンは、aws-cliとCloudFormationで作成しました。
aws-cli コマンドをMakefileに記述しているので、cfnディレクトリで下記コマンドを実行するとCloudFormationのスタックを作成します。
make に指定する変数は以下のとおり
  • DOMAIN_NAME・・・独自ドメイン名
$ make cfnr53 DOMAIN_NAME=blue21.tk

コマンドが終了すると、
Route53に下図のようなホストゾーンが作成されます。


Route53のネームサーバをドメイン・プロバイダに登録後、下記コマンドで変更が反映されたことを確認します。
ネームサーバが表示されたらOKです。
$ dig blue21.tk @8.8.8.8 NS

; <<>> DiG 9.16.1-Ubuntu <<>> blue21.tk @8.8.8.8 NS
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2465
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;blue21.tk.                     IN      NS

;; ANSWER SECTION:
blue21.tk.              21588   IN      NS      ns-1424.awsdns-50.org.
blue21.tk.              21588   IN      NS      ns-1611.awsdns-09.co.uk.
blue21.tk.              21588   IN      NS      ns-499.awsdns-62.com.
blue21.tk.              21588   IN      NS      ns-751.awsdns-29.net.

;; Query time: 20 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Sep 10 09:58:04 JST 2020
;; MSG SIZE  rcvd: 178

4. IPアドレスの確認


CloudFrontにアクセスするクライアント(私の場合は自宅)のIPアドレスを確認します。
下記URLで、ipv4 と ipv6 のIPアドレスを確認できます。
下図は表示例です。
確認したIPアドレスは、後続の手順で使用します。


4.CloudFront+S3+WAF+ACM構築


構築は、aws-cliとCloudFormationで行います。
aws-cliのコマンドはMakefileに記述しています。
cfnディレクトリで、下記のmakeコマンドを実行すると、CloudFormationのスタックを作成します。
make に指定する変数は以下のとおり
  • DOMAIN_NAME・・・独自ドメイン名
  • HOSTED_ZONE_ID・・・Route53に作成したホストゾーンのID
  • ALLOW_CIDR_V4・・・アクセスを許可するパブリックIPアドレス(ipv4)
  • ALLOW_CIDR_V6・・・アクセスを許可するパブリックIPアドレス(ipv6)
$ make cfncf DOMAIN_NAME=blue21.tk HOSTED_ZONE_ID=Zxxx ALLOW_CIDR_V4=1.2.3.4/32 ALLOW_CIDR_V6=2001:xxx/128

上記コマンド終了後、
cfnディレクトリで下記コマンドを実行すると、作成したスタック一覧を参照できます。
このコマンドはスタック名に "Blue21" を含むものをリストします。
$ aws cloudformation describe-stacks \
--profile blue21 \
--query 'Stacks[?contains(StackName,`Blue21`)].{StackName:StackName,StackStatus:StackStatus,Desc:Description}' \
--output table
----------------------------------------------------------
|                     DescribeStacks                     |
+-----------------+------------------+-------------------+
|      Desc       |    StackName     |    StackStatus    |
+-----------------+------------------+-------------------+
|  CloudFront & S3|  Blue21Cf00PoC   |  CREATE_COMPLETE  |
|  R53 public zone|  Blue21R5300PoC  |  CREATE_COMPLETE  |
+-----------------+------------------+-------------------+

動作確認


AWS環境確認


Route53のレコードは下図のとおり。
AレコードとAAAAレコードを作成しています。


ACMの証明書は下図のとおり。


WAF(v2)は下図のとおり。


CloudFrontは下図のとおり
  • オリジンはS3のみ。
  • S3バケットにアクセスログを格納します。
  • WAFでブロックした場合、WAFは403を返しますが、CloudFrontで503に変更して、エラーページを返すようにしています。


S3バケットは下図のとおり。
なお、S3バケットには2つのディレクトリが存在します。
  • cflogs ・・・ CloudFrontのアクセスログ格納
  • htdocs ・・・ 静的なWEBコンテンツ格納


サンプルのWEBコンテンツをデプロイ


動作確認用のサンプルページをS3バケットにアップロードします。
aws-cli コマンドをMakefileに記述しているので、
cfnディレクトリで下記コマンドを実行するとアップロードできます。
$ make s3deploy

下記コマンドで、S3バケットの中身をリストできます。
2つの html ファイルがあればOKです。
$ make s3ls
aws s3 ls s3://blue21cf00poc-s3bucket-2nwhp37a58d4/htdocs/ --recursive --profile blue21
2020-09-10 16:20:11        212 htdocs/index.html
2020-09-10 16:20:11        214 htdocs/sorry.html

ipv4 でリクエスト


ipv4のみ対応のネットワーク(WSL2のubuntu)から、curlコマンドでCloudFrontにリクエストします。
$ curl https://blue21.tk

CloudFront(https://blue21.tk)にリクエストすると
下図のように CloudFrontアクセスログには ipv4のIPアドレスが記録されます。


CloudFront(https://www.blue21.tk)にリクエストしても、上記と同じようにipv4のIPアドレスがログに記録されます。

ipv6でリクエスト


ipv4/ipv6対応のネットワーク(Windows10)から、ChromeブラウザでCloudFrontにリクエストします。(下図のように表示されます)


CloudFront(https://blue21.tk)にリクエストすると
下図のように CloudFrontアクセスログには ipv6のIPアドレスが記録されます。


CloudFront(https://www.blue21.tk)にリクエストすると、ipv4のIPアドレスがログに記録されます。
CloudFrontは ipv6 有効化していますが、www.blue21.tk は AAAAレコードを登録していないので名前解決できず、ipv4のIPアドレスで通信しています。

環境破棄


AWSコンソールで下記のスタックを上から順番に削除します。
  • スタック(Blue21Cf00PoC)を削除する前に、S3バケットを空にしないと失敗します
  • スタック(Blue21R5300PoC)を削除する前に、ACM用のCNAMEレコードを削除しないと失敗します。
----------------------------------------------------------
|                     DescribeStacks                     |
+-----------------+------------------+-------------------+
|      Desc       |    StackName     |    StackStatus    |
+-----------------+------------------+-------------------+
|  CloudFront & S3|  Blue21Cf00PoC   |  CREATE_COMPLETE  |
|  R53 public zone|  Blue21R5300PoC  |  CREATE_COMPLETE  |
+-----------------+------------------+-------------------+


2020年9月7日月曜日

ECS(laravel)のCI/CDとログ収集(laravel.log)を試す


以下のこと試してみました。
  • CloudFromation, rainで検証環境を作る。
  • ローカル環境(docker)で、ngin,php-fpm(laravel)を動かす。
  • AWS環境のECSで、ngin,php-fpm(laravel)を動かす。
  • AWS環境のECSは、Bitbucket,CodePipelineでCI/CDする。
  • laravel.log をCloudWatchLogsで見る。


検証環境の構成は下図のとおり。


作業環境(ローカル環境)


構築作業は、WSL2(ubuntu20.04)で実施します。
なお、AWSのリージョンは us-east-1を使用します。
  • Windows10
  • WSL2(ubuntu20.04)
  • docker 19.03.12
  • make 4.2.1
  • aws-cli 2.1.31
  • rain 1.1.2

rain


CloudFormationのCLI実行ツールです。rainについては下記URL参照
今回は、Makefileにrainコマンドを記載して使用します。

aws-cli


rain, aws-cli実行時はプロファイルを指定します。プロファイルの設定は下記のとおり。
  • blue21 という名称でプロファイルを作成
  • リージョンは us-east-1 を使用し, Output形式は json
  • 管理者権限のアクセスキーを使用

ソース一式のdownload


環境構築に必要なCloudFormationテンプレートなどは、Bitbucketに登録しています。
Bitbucketの下記URLからダウンロードできます。

ローカルのDocker環境構築


CloudFormationを実行し、VPCとECRを構築します。
ローカル環境で必要なのはECRだけですが、VPCもついでに構築します。
$ cd <InstallDir>/aws/cfn
$ make -f Makefile.rain cfnstep1 RAINOPT="-y"


作成したスタックを確認します。

$ make -f Makefile.rain stackls
aws cloudformation describe-stacks \
--profile blue21 \
--query 'Stacks[?contains(StackName,`Blue21`)].{StackName:StackName,StackStatus:StackStatus,Desc:Description}' \
--output table
-----------------------------------------------------
|                  DescribeStacks                   |
+--------------+----------------+-------------------+
|     Desc     |   StackName    |    StackStatus    |
+--------------+----------------+-------------------+
|  ECR         |  Blue21EcrPoC  |  CREATE_COMPLETE  |
|  VPC & subnet|  Blue21VpcPoC  |  CREATE_COMPLETE  |
+--------------+----------------+-------------------+


Dockerイメージをビルドし、ローカル環境用としてECRへPushします。

$ cd <InstallDir>/docker
$ make build
$ make push_local


ECRにPushしたイメージは下記のとおり。
nginx, php-fpm の2つです。imageTagに"local"を設定しています。

$ aws ecr list-images --repository-name blue21/nginx --profile blue21
{
    "imageIds": [
        {
            "imageDigest": "sha256:c52d95ceaee7842700ea4449128f90afd7bb440372ac6a569ecf7160ee5523db",
            "imageTag": "local"
        }
    ]
}
$ aws ecr list-images --repository-name blue21/php-fpm --profile blue21
{
    "imageIds": [
        {
            "imageDigest": "sha256:48d720bcb6bf55036ef10a00139231e510104951c3e30db376cc54764dee6da4",
            "imageTag": "local"
        }
    ]
}


docker環境を起動します。

イメージは、ECRからPullします。

$ cd <InstallDir>/docker
$ make up


ローカル環境のデモサイトにアクセスして動作確認します。

$ curl -I http://localhost
HTTP/1.1 200 OK
Server: nginx/1.19.0
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/7.4.13
Cache-Control: no-cache, private
Date: Sat, 06 Mar 2021 12:35:08 GMT
Set-Cookie: XSRF-TOKEN=eyJpdiI6Ikd2eUdKcUMwbTZsTC93bDZ1cDBNd3c9PSIsInZhbHVlIjoidHIwNEE3b3lkZWVZRXZLaFdkbWUySWRLYSt2MkNBVlF3L1o4VDV5dDluZXFpNm9IM1BNNHdKUlM1bFhBeE5CY3cxdXdjVEV6ci9zQmE5by8zOC9DQzZNRTZYaXpMdElFMFRsZG9tUDFxTzN0SzRRYjhnVmxOMUdVTzZvL0ozQi8iLCJtYWMiOiI3ZjRiZWQxNDBmZDFkZTE5ZjAyMGY1MjI5ZmQwNWU2ZjBlMGVjM2MzNzU4MGU3NmY0ZDFmYTVhM2U2MjRiM2I5In0%3D; expires=Sat, 06-Mar-2021 14:35:08 GMT; Max-Age=7200; path=/; samesite=lax
Set-Cookie: laravel_session=eyJpdiI6ImJLaFQzblhpcllSWTRQUERFZDZQUXc9PSIsInZhbHVlIjoicHlkR2FEMjIxSTJqajdwVmZxZkFMMURTanllV0lXQmFSVlR6emkzK0Exak1BVTIvbnh0MkVTbDJiM25wT1FTMzQ4MmI0V0RQRlp1dVZmZEdYeUxkUXZSZ2RFMmpMQ0ZnL3BGeTlMYmYxblJ2d3NSQ2RVTnBTSGowcDZQQjFvZkwiLCJtYWMiOiJkODk1YWVjYWQ0OTc2MmYxODkxOWQ1MTA2NDA0NjM3YzUxMmE3MzZiYzdiN2M0ZDc0OTA1ZjkyYzMzYWZjOWFiIn0%3D; expires=Sat, 06-Mar-2021 14:35:08 GMT; Max-Age=7200; path=/; httponly; samesite=lax

下記URLにアクセスすると、laravel.log へデバッグ用のログを出力します。
$ curl http://localhost/log_test
log test


laravel.log の内容を確認します。

$ tail -1 app/storage/logs/laravel.log
[2021-03-06 12:37:09] local.INFO: ログ出力テスト


Docker環境を停止します。

$ cd <InstallDir>/docker
$ make down


DockerHubのトークン作成


CI/CDでは、DockerHubからnginx, php-fpmのイメージをPullします。
DockerHubからPullできる回数には制限がありますが、匿名ユーザは、「6時間あたり100回」のようです。
今回、CodeBuildを使用していますが、匿名ユーザだと回数制限でPullがエラーになることがありました。
(詳しくは、下記URL参照)
なので、DockerHubの無料ユーザでを使うことにしました。
無料ユーザは「6時間あたり200回」のようです。

CodeBuildでは、DockerHubの無料アカウントでトークン作成して使用します。
「AccountSettings」からトークンを作成できます。



DockerHubのユーザIDとトークンは、AWSコンソールを使用してSSMパラメータストアに登録します。



CodestarConnectionの作成


今回、CodePipelineでBitbucketからソースを取得するのにCodestarConnectionを使用します。
AWSコンソールでCodestarConnection を作成します。
ARNを、後続のステップで使用します。



ECS及びCI/CD環境構築


ECSで使用するDockerイメージをECRにPushします。
$ cd <InstallDir>/docker
$ make push_base

ECRにPushしたイメージは下記のとおり。
imageTagに"poc"を設定しています。

$ aws ecr list-images --repository-name blue21/nginx --profile blue21
{
    "imageIds": [
        {
            "imageDigest": "sha256:c52d95ceaee7842700ea4449128f90afd7bb440372ac6a569ecf7160ee5523db",
            "imageTag": "local"
        },
        {
            "imageDigest": "sha256:c52d95ceaee7842700ea4449128f90afd7bb440372ac6a569ecf7160ee5523db",
            "imageTag": "poc"
        }
    ]
}
$ aws ecr list-images --repository-name blue21/php-fpm --profile blue21
{
    "imageIds": [
        {
            "imageDigest": "sha256:48d720bcb6bf55036ef10a00139231e510104951c3e30db376cc54764dee6da4",
            "imageTag": "poc"
        },
        {
            "imageDigest": "sha256:48d720bcb6bf55036ef10a00139231e510104951c3e30db376cc54764dee6da4",
            "imageTag": "local"
        }
    ]
}

SSMパラメータストアにパラメータをPutします。
$ cd <InstallDir>/aws/ssm
$ make ssmput_all

登録したパラメータは以下のとおり。
$ make ssmlist
aws ssm describe-parameters \
--profile blue21 \
--query Parameters[].Name --filters "Key=Name,Values=/blue21/"
[
    "/blue21/app/poc/.env",
    "/blue21/cwa/poc/config",
    "/blue21/nginx/poc/.htpasswd",
    "/blue21/nginx/poc/Dockerfile",
    "/blue21/nginx/poc/default.conf",
    "/blue21/php-fpm/poc/Dockerfile",
    "/blue21/php-fpm/poc/php.ini",
    "/blue21/php-fpm/poc/www.conf"
]

上記で作成したCodestarConnectionのARNを指定して、ECS環境とCICD環境を構築します。
$ cd <InstallDir>/aws/cfn
$ make -f Makefile.rain cfnstep2 RAINOPT="-y" CODESTAR="CodestarConnectionのArn"


作成したスタックは下記のとおり。

$ make -f Makefile.rain stackls
aws cloudformation describe-stacks \
--profile blue21 \
--query 'Stacks[?contains(StackName,`Blue21`)].{StackName:StackName,StackStatus:StackStatus,Desc:Description}' \
--output table
-------------------------------------------------------------------------------------
|                                  DescribeStacks                                   |
+----------------------------------------------+----------------+-------------------+
|                     Desc                     |   StackName    |    StackStatus    |
+----------------------------------------------+----------------+-------------------+
|  CodePipeline For ECS Fargate with Bitbucket |  Blue21CicdPoC |  CREATE_COMPLETE  |
|  Ecs service                                 |  Blue21SvcPoC  |  CREATE_COMPLETE  |
|  Ecs cluster                                 |  Blue21EcsPoC  |  CREATE_COMPLETE  |
|  ALB                                         |  Blue21AlbPoC  |  CREATE_COMPLETE  |
|  ECR                                         |  Blue21EcrPoC  |  CREATE_COMPLETE  |
|  VPC & subnet                                |  Blue21VpcPoC  |  CREATE_COMPLETE  |
+----------------------------------------------+----------------+-------------------+

CodePipeline構築が成功すると、CodePipelineが自動的に実行されます。



ECSタスク起動


初期状態は、ECSサービスの「必要数」が0です。

「必要数」を1に変更して、タスクを起動します。

まず、ECRに登録したイメージのタグを確認します。

$ aws ecr list-images --repository-name blue21/php-fpm --profile blue21
{
    "imageIds": [
        {
            "imageDigest": "sha256:73bf5ee98aebb6526c7c25f4a51080ba2a5c33e3761d5e37067c7ca186d9eb0a",
            "imageTag": "poc-1-7759e75"
        },
        {
            "imageDigest": "sha256:48d720bcb6bf55036ef10a00139231e510104951c3e30db376cc54764dee6da4",
            "imageTag": "poc"
        },
        {
            "imageDigest": "sha256:48d720bcb6bf55036ef10a00139231e510104951c3e30db376cc54764dee6da4",
            "imageTag": "local"
        }
    ]
}

poc, local はアプリケーションを含んでいないので、ECSでは使用できません。

上記例だと、"poc-1-7759e75"がECS用の最新です。

CodeBuildで作成したイメージのタグの命名規則は、「POC-<ビルド番号>-<コミットID>」となります。


下記のように「必要数」と「イメージタグ」を指定してCloudFormationを実行します。

$ make -f Makefile.rain cfnsvc00 ECS_COUNT=1 ECS_TAG=poc-1-7759e75


タスク起動に成功すると、AWSコンソールでは下図のように表示されます。


タスク起動に失敗すると、タスクの再起動を永遠に繰り返します。AWSコンソールで「必要数」を0に変更すれば、再起動を停止できます。


ECSのデモサイト確認


ECSで起動中のデモサイトにアクセスします。

まず、ALBのDNS名を確認します。


ブラウザで下記URLのデモサイトにアクセスします。
  • http://<ALBのDNS名>
BASIC認証でアクセス制限しているので下記IDとパスワードでログインします。
BASIC認証のIDとパスワードは、SSMパラメータストアで管理しています。
  • ユーザ:blue21
  • パスワード:test

ログイン後、下図のように表示されたら成功です。



ログ確認


ブラウザで下記URLのデモサイトにアクセスします。
  • http://<ALBのDNS名>/log_test


CloudWatchLogsの/blu21/app でログ出力を確認します。

下図のように表示されたら成功です。



CI/CD確認


mastarブランチで空コミットしてPushします。

$ cd <InstallDir>
$ git commit --allow-empty -m "cicd test"
$ git push


自動的にCodePipelineが実行されます。

CodePipelineの画面には、下図のように、上記のコミットIDが表示されます。


CodePipelineが正常終了後、ECSタスクを確認して、下図のようにCodepipelineと同じコミットIDが表示されたら成功です。





2020年9月6日日曜日

Docker環境のWP Offload Mediaでminio(s3互換)に画像を保存する


Docker環境のWordPressで、WP Offload Mediaプラグインを試します。

WP Offload Mediaプラグインを使用すると、画像を AWSのS3に保存できますが、今回は、
S3互換の minioを使用して、Docker環境のminioに画像を保存したいと思います。
下記URLを参考にしました。


作業環境


私の作業環境は以下のとおり。
Windows10のWSL2(ubuntu20.04)で作業します。
なお、Dockerは、ubuntuにインストールして起動しています。(Docker Desktopではない)
  • Windows10
  • WSL2(ubuntu20.04)
  • docker 19.03.12

docker-compose


WordPressの検証環境を docker-compose で作ります。
docker-compose.yml を下記のように作成しました。
version: "3"
services:

  # wordpress
  wordpress:
    image: wordpress
    container_name: wordpress
    hostname: wordpress
    volumes:
      - ./wordpress:/var/www/html
    restart: always
    depends_on:
      - mysql
    network_mode: host
    expose:
      - 80
    environment:
      WORDPRESS_DB_HOST: 172.30.0.10:3306
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: P@ssw0rd

  # aws s3 mock
  minio:
    image: minio/minio
    container_name: minio
    hostname: minio
    volumes:
      - ./minio:/data
    restart: always
    network_mode: host
    expose:
      - 9000
    environment:
      MINIO_ACCESS_KEY: key00000
      MINIO_SECRET_KEY: key00000
    command: server /data

  # mysql database
  mysql:
    image: mysql:5.7
    container_name: mysql
    hostname: mysql
    volumes:
      - ./mysql:/var/lib/mysql
    restart: always
    networks:
      app_net:
        ipv4_address: 172.30.0.10
    environment:
      MYSQL_ROOT_PASSWORD: P@ssw0rd!!
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: P@ssw0rd

  # mysql admin gui
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: phpmyadmin
    hostname: phpmyadmin
    restart: always
    links:
      - mysql
    ports:
      - 18080:80
    networks:
      app_net:
        ipv4_address: 172.30.0.20
    environment:
      PMA_HOST: 172.30.0.10

networks:
  app_net:
    driver: bridge
    ipam:
     driver: default
     config:
       - subnet: 172.30.0.0/24

下記のようにコマンドを実行すると起動します。
$ docker-compose up -d


各画面には下記URLでアクセスします。
  • WordPress
    http://localhost
  • minio (S3互換)
    http://localhost:9000
  • PhpMyAdmin (MySQL管理)
    http://localhost:18080

WordPressのインストール


ブラウザで http://localhost にアクセスすると、下図のように表示されます。
[日本語]を選択して[次へ]をクリック


下図のように入力して[WordPressをインストール]をクリック


下図のように表示されたらインストール完了です。


WP Offload Mediaプラグインのインストール


WordPress管理画面の[プラグイン]>[新規追加]をクリックすると下図の画面が表示されます。
[キーワード]に "s3" を入力すると、「WP Offload Mediaプラグイン」が表示されるので、[今すぐインストール]をクリックします。


インストールが完了したら[有効化]をクリックします。


wp-config.php を修正します。
ファイルの場所は、"<docker-compose.ymlのあるDIR>/wordpress/wp-config.php" です。
下記のように docker-compose.yml で設定したアクセスキー、シークレットキーを追記します。
define( 'AS3CF_SETTINGS', serialize( array(
        'provider' => 'aws',
        'access-key-id' => 'key00000',
        'secret-access-key' => 'key00000',
) ) );
追記した場所は下図のとおり。


WP Offload Media Tweaksプラグインのインストール


プラグインのディレクトリに移動します。
$ <docker-compose.ymlのあるDIR>/wordpress/wp-content/plugins

下記コマンドでプラグインをダウンロードし、オーナを変更します。
$ sudo git clone https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront-tweaks.git
$ sudo chown -R www-data.www-data wp-amazon-s3-and-cloudfront-tweaks

WordPressの管理画面でプラグインを有効化します。


下記ファイルを修正します。
  • <docker-compose.ymlのあるDIR>/wordpress/wp-content/plugins/wp-amazon-s3-and-cloudfront-tweaks/amazon-s3-and-cloudfront-tweaks.php
add_filter のコメントを下図のように削除して有効化します。
minio_s3_url_domain のフィルタは、エラーになるので有効にしませんでした。


minio_s3_client_args() のホスト名とポートを下図のように変更します。


minio_s3_url_domain() のホスト名とポートを下図のように変更します。
(たぶん、必要ないと思うが、念のため変更しました)


minio_s3_console_url() のホスト名とポートを下図のように変更します。


WP Offload Mediaプラグインの設定


WordPress管理画面でプラグインの設定を開きます。


試しに[Browse existing buckets]をクリックして、既存のバケットを表示してみます。


まだ、minioにはバケットを作成していないので、
当然、バケットは存在しません。
[Create new bucket] をクリックしてバケットを作成します。


[Bucket]にバケット名を入力して、[Create New Bucket]をクリックします。


下図のように表示されたら設定完了です。


動作確認


WordPressの管理画面で[メディア]から画像をアップロードします。
メディアライブラリにアップロードした画像が表示されたらOKです。


アップロードした画像の詳細を参照し、
ファイルのURLが minio (http://localhost:9000)であればOKです。


minio を見てみます。
http://localhost:9000 にアクセスします。
docker-compose.yml に記載したアクセスキー(key00000)とシークレットキー(key00000)でログインできます。


下図のように画像が格納されていればOKです。











2020年9月5日土曜日

ワンライナーでコマンド実行結果をdiffする


下記のようにすると、コマンドの実行結果を比較できます。

下記の例では、ファイルからコメント行を除外して diff してます。

ubuntu@MyComputer:~$ diff -y <(egrep -v '^#' /etc/hosts) <(egrep -v '^#' test.txt)
127.0.0.1       localhost                                     | 127.0.0.1       localhost
127.0.1.1       MyComputer.localdomain  MyComputer            <
127.0.0.1       webgoat.local                                 <
                                                              <
::1     ip6-localhost ip6-loopback                            <
fe00::0 ip6-localnet                                          <
ff00::0 ip6-mcastprefix                                       <
ff02::1 ip6-allnodes                                          <
ff02::2 ip6-allrouters                                        <





2020年9月4日金曜日

CloudFromationでRDS(MySQL)のDBとユーザを複数作る

 

CloudFormationのカスタムリソース(AWS Lambda-backend)を使用して、RDS(MySql)に複数のデータベースとユーザを作成してみました。


検証環境の構成イメージは下図のとおり。


[MEMO]
  • CloudFormationでのRDS構築時、MasterUserの名前とパスワードは、SSMのパラメータストアから取得します。
  • Lamdaは sam-cli でデプロイします。
  • CloudFormationのカスタムリソースからLambdaを実行して、MySqlのdatabaseとuserを作成します。
  • ECSのfargate spot で PhpMyAdmin を実行し、DBの確認に使用します。このPhpMyAdminは、特定IPアドレスからのアクセスのみ許可します。

MySqlにDBとユーザを作成するカスタムリソースは、下記のように定義します。
  • ServiceToken・・・Lambdaのarn
  • DbHost・・・RDSのエンドポイント
  • DbName・・・Database名
  • DbUserName・・・DBユーザ名
  • DbUserPassword・・・DBユーザのパスワード
  • DbMsterUser・・・RDSのマスターユーザ名
  • DbMasterPassword・・・RDSのマスターユーザのパスワード
  DB01:
    Type: Custom::CustomMysql
    Version: 1.0
    Properties:
      ServiceToken: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunc}"
      DbHost: { "Fn::ImportValue": !Sub "${StackNameRds}-Endpoint" }
      DbName: "demo01"
      DbUserName: "demouser01"
      DbUserPassword: "demodemo02"
      DbMasterUser: '{{resolve:ssm:/blue21/rds/masteruser/name:1}}'
      DbMasterPassword: '{{resolve:ssm:/blue21/rds/masteruser/password:1}}'

カスタムリソースから実行するLambdaは下記のとおり。
  • スタック作成(Create)は、DB・ユーザを作成します。ユーザには作成したDBの全権を与えます。
  • スタック更新(Update)は、ユーザ・パスワード変更のみ実施します。
  • スタック削除(Delete)は、何もしません。
import json, logging, os
import pymysql.cursors
import cfnresponse

logger = logging.getLogger()
level = logging.getLevelName(os.environ.get('LOG_LEVEL', logging.DEBUG))
logger.setLevel(level)

def lambda_handler(event, context):

    logger.debug(event)

    try:
        response_data = {}
        params = dict([(k, v) for k, v in event['ResourceProperties'].items() if k != 'ServiceToken'])
        logger.debug(params)

        conn = pymysql.connect(
            host=params['DbHost'],
            user=params['DbMasterUser'],
            passwd=params['DbMasterPassword'],
            cursorclass=pymysql.cursors.DictCursor,
            connect_timeout=3
        )
        cursor = conn.cursor()

        #if event['RequestType'] == 'Delete':
        if event['RequestType'] == 'Create':
            db_setup(cursor, params)
        elif event['RequestType'] == 'Update':
            old_params = dict([(k, v) for k, v in event['OldResourceProperties'].items() if k != 'ServiceToken'])
            logger.debug(old_params)
            db_update(cursor, params, old_params)

        cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)

    except Exception as e:
        logger.debug(e)
        cfnresponse.send(event, context, cfnresponse.FAILED, {})

    finally:
        conn.close()

def db_setup(cursor, params):

    # create database
    if 'DbName' in params:
        sql = "create database if not exists %s DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci" % params['DbName']
        logger.debug(sql)
        cursor.execute(sql)

    # create User
    if 'DbUserName' in params and 'DbUserPassword' in params:
        sql = "create user if not exists '%s'@'%%' IDENTIFIED BY '%s'" % (params['DbUserName'], params['DbUserPassword'])
        logger.debug(sql)
        cursor.execute(sql)

    # grant
    if 'DbName' in params and 'DbUserName' in params:
        sql = "grant all on %s.* to '%s'@'%%'" % (params['DbName'], params['DbUserName'])
        logger.debug(sql)
        cursor.execute(sql)

def db_update(cursor, new, old):

    # rename user
    if old['DbUserName'] != new['DbUserName']:
        sql = "rename user '%s'@'%%' to '%s'@'%%'" % (old['DbUserName'], new['DbUserName'])
        logger.debug(sql)
        cursor.execute(sql)

    # change password
    if old['DbUserPassword'] != new['DbUserPassword']:
        sql = "alter user '%s'@'%%' identified by '%s'" % (new['DbUserName'], new['DbUserPassword'])
        logger.debug(sql)
        cursor.execute(sql)

環境構築


## 作業環境(ローカル環境)

構築作業は、WSL2(ubuntu20.04)で実施します。
なお、AWSのリージョンは us-east-1を使用します。
  • Windows10
  • WSL2(ubuntu20.04)
  • docker 19.03.12
  • make 4.2.1
  • aws-cli 1.17.14
  • sam-cli 0.53.0

1. aws-cli の設定


aws-cli実行時及び、sam-cliのデプロイ時はプロファイルを指定します。プロファイルの設定は下記のとおり。
  • blue21 という名称でプロファイルを作成
  • リージョンは us-east-1 を使用し, Output形式は json
  • 管理者権限のアクセスキーを使用

2.SSMパラメータストアの設定


SSMパラメータストアにRDSのマスタユーザ名とパスワードを設定します。
cfnテンプレート(cfn_rds.yml/cfn_custom_mysql.yml)で使用する際に、バージョンNoを1で指定しています。必要に応じてテンプレートのバージョンを修正してください。
  • /blue21/rds/masteruser/name・・・マスターユーザの名前を設定
  • /blue21/rds/masteruser/password・・・マスターユーザのパスワードを設定

3. ソース一式のdownload


環境構築に必要なCloudFormationテンプレートなどは、Bitbucketに登録しています。
Bitbucketの下記URLからダウンロードできます。

レポジトリの内容は以下のとおり。
.
├── README.md
├── bin
│   ├── Makefile
│   ├── README.md
│   ├── cfn_validate.sh
│   └── cfn_wait.sh
├── cfn
│   ├── Makefile
│   ├── README.md
│   ├── cfn_custom_mysql.yml
│   ├── cfn_ecs_cluster.yml
│   ├── cfn_ecs_service.yml
│   ├── cfn_lambda.yml
│   ├── cfn_rds.yml
│   └── cfn_vpc.yml
├── img
│   ├── image01.drawio
│   └── image01.png
└── lambda
    ├── Makefile
    ├── README.md
    ├── app
    │   ├── app.py
    │   └── requirements.txt
    ├── event_create.json
    ├── event_update.json
    ├── mysql
    │   └── docker-compose.yml
    ├── samconfig.toml
    └── template.yaml

4.CloudFormation実行


構築は、aws-cli, sam-cli, CloudFormationで行います。
aws-cli, sam-cli のコマンドはMakefileに記述しています。
cfnディレクトリで、下記のmakeコマンドを実行すると、CloudFormationのスタックを作成します。
ALLOW_CIDRには、PhpMyAdminへのアクセスを許可するパブリックIPアドレスを指定します。
$ make cfnall ALLOW_CIDR=1.2.3.4/32

cfnディレクトリで下記コマンドを実行すると、作成したスタック一覧を参照できます。
このコマンドはスタック名に "Blue21" を含むものをリストします。
$ make stackls
aws cloudformation describe-stacks \
--profile blue21 \
--query 'Stacks[?contains(StackName,`Blue21`)].{StackName:StackName,StackStatus:StackStatus,Desc:Description}' \
--output table
---------------------------------------------------------------------------------------
|                                   DescribeStacks                                    |
+--------------------------------------------+--------------------+-------------------+
|                    Desc                    |     StackName      |    StackStatus    |
+--------------------------------------------+--------------------+-------------------+
|  Mysql create db & user. (custom resource) |  Blue21MySQL00PoC  |  CREATE_COMPLETE  |
|  Mysql create db & user. (lambda-backend)  |  Blue21Lambda01PoC |  CREATE_COMPLETE  |
|  Role,SG,S3 for Lambda                     |  Blue21Lambda00PoC |  CREATE_COMPLETE  |
|  Ecs service                               |  Blue21Svc00PoC    |  CREATE_COMPLETE  |
|  Ecs cluster (FargateSpot)                 |  Blue21Ecs00PoC    |  CREATE_COMPLETE  |
|  RDS (mysql)                               |  Blue21Rds00PoC    |  CREATE_COMPLETE  |
|  VPC & subnet                              |  Blue21Vpc00PoC    |  CREATE_COMPLETE  |
+--------------------------------------------+--------------------+-------------------+

動作確認


AWS環境確認


カスタムリソースで使用するLambdaは下図のとおり


RDSは下図のとおり


ECSは下図のとおり
構築直後は、ECSタスク(FARGATE)を起動していません。


ECSタスクに設定したセキュリティグループは下図のとおり。
ECSタスク(PhpMyAdmin)にインターネットからアクセスする場合は、ここで設定した特定IPアドレスのみ接続を許可されます。


RDSのMySql確認


MySqlの確認は、PhpMyAdminを使用します。
ECSサービスの「必要数」を "1" に変更して、タスク(PhpMyAdmin)を起動します。
ローカルのcfnディレクトリで下記コマンドを実行すると CloudFormationで「必要数」を変更します。
$ make cfnsvc CFNCMD=update-stack ECS_COUNT=1
コマンドが終了するとタスクが起動します。
タスクは、FargateSpotで起動します。


タスクのパブリックIPアドレスを確認します。


ブラウザでPhpMyAdminのURL(http://<タスクのパブリックIPアドレス>)を指定してWEB画面を開きます。
RDSのマスターユーザでログインします。


データベースを確認します。
「demo01」は、カスタムリソース(lambda-backend)が作成したデータベースです。


ユーザを確認します。
「demouser01」は、カスタムリソース(lambda-backend)が作成したユーザです。


ちなみに、demouser01 で PhpMyAdmin にログインできます。



[MEMO]

環境破棄


AWSコンソールで下記のスタックを上から順番に削除します。
なお、スタック(Blue21Lambda00PoC)を削除する前に、S3バケットを空にしないと削除に失敗します。
---------------------------------------------------------------------------------------
|                                   DescribeStacks                                    |
+--------------------------------------------+--------------------+-------------------+
|                    Desc                    |     StackName      |    StackStatus    |
+--------------------------------------------+--------------------+-------------------+
|  Mysql create db & user. (custom resource) |  Blue21MySQL00PoC  |  CREATE_COMPLETE  |
|  Mysql create db & user. (lambda-backend)  |  Blue21Lambda01PoC |  CREATE_COMPLETE  |
|  Role,SG,S3 for Lambda                     |  Blue21Lambda00PoC |  CREATE_COMPLETE  |
|  Ecs service                               |  Blue21Svc00PoC    |  CREATE_COMPLETE  |
|  Ecs cluster (FargateSpot)                 |  Blue21Ecs00PoC    |  CREATE_COMPLETE  |
|  RDS (mysql)                               |  Blue21Rds00PoC    |  CREATE_COMPLETE  |
|  VPC & subnet                              |  Blue21Vpc00PoC    |  CREATE_COMPLETE  |
+--------------------------------------------+--------------------+-------------------+