下記のaws-cliプラグインを使用すると、プロファイル上のサービスごとにエンドポイントを設定することができるらしい。
「localstack」や「minio」を使用するときに便利かもしれない。
下記は、 ~/.aws/config にエンドポイントを設定した例です。
[profile local] dynamodb = endpoint_url = http://localhost:8000
下記のaws-cliプラグインを使用すると、プロファイル上のサービスごとにエンドポイントを設定することができるらしい。
「localstack」や「minio」を使用するときに便利かもしれない。
下記は、 ~/.aws/config にエンドポイントを設定した例です。
[profile local] dynamodb = endpoint_url = http://localhost:8000
下記のように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"}
[MEMO]
. ├── 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
$ make cfnr53 DOMAIN_NAME=blue21.tk
$ 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
$ make cfncf DOMAIN_NAME=blue21.tk HOSTED_ZONE_ID=Zxxx ALLOW_CIDR_V4=1.2.3.4/32 ALLOW_CIDR_V6=2001:xxx/128
$ 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 | +-----------------+------------------+-------------------+
$ make s3deploy
$ 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
$ curl https://blue21.tk
---------------------------------------------------------- | DescribeStacks | +-----------------+------------------+-------------------+ | Desc | StackName | StackStatus | +-----------------+------------------+-------------------+ | CloudFront & S3| Blue21Cf00PoC | CREATE_COMPLETE | | R53 public zone| Blue21R5300PoC | CREATE_COMPLETE | +-----------------+------------------+-------------------+
検証環境の構成は下図のとおり。
$ 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
$ 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
$ 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" } ] }
$ 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" ]
$ 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サービスの「必要数」が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で起動中のデモサイトにアクセスします。
まず、ALBのDNS名を確認します。
CloudWatchLogsの/blu21/app でログ出力を確認します。
下図のように表示されたら成功です。
mastarブランチで空コミットしてPushします。
$ cd <InstallDir> $ git commit --allow-empty -m "cicd test" $ git push
自動的にCodePipelineが実行されます。
CodePipelineの画面には、下図のように、上記のコミットIDが表示されます。
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
define( 'AS3CF_SETTINGS', serialize( array( 'provider' => 'aws', 'access-key-id' => 'key00000', 'secret-access-key' => 'key00000', ) ) );
$ <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
下記のようにすると、コマンドの実行結果を比較できます。
下記の例では、ファイルからコメント行を除外して 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 <
CloudFormationのカスタムリソース(AWS Lambda-backend)を使用して、RDS(MySql)に複数のデータベースとユーザを作成してみました。
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}}'
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)
. ├── 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
$ make cfnall ALLOW_CIDR=1.2.3.4/32
$ 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 | +--------------------------------------------+--------------------+-------------------+
$ make cfnsvc CFNCMD=update-stack ECS_COUNT=1
--------------------------------------------------------------------------------------- | 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 | +--------------------------------------------+--------------------+-------------------+