【AWS】ReactをCodepipeineでS3にデプロイ
1. 使用するサービス
(AWS)
- CodePipeline
- CodeBuild
- S3
2.概要
GitHubで管理しているReactソースを指定したブランチをプッシュするとそのブランチの中のReactソースをビルドしてS3にデプロイしてくれる流れを説明します。
前提としてS3は作成済で、GitHubなどソース管理のツールは導入済として特に説明はしません。
CodePipelineの説明は過去記事参照。
buffalokusojima.hatenablog.com
3. 実装
GitHubやCodeCommitのブランチのプッシュに対するCodePipelineのキックは上記の過去記事参考。
CodeBuildから説明します。
3-1. CodeBuildの設定
基本的に過去記事通りで大丈夫です。buildspec.ymlの中は以下のようにします。
Reactソースはappフォルダにある事にして書いています。適宜編集お願いします。
version: 0.2 phases: install: commands: - n stable - npm update -g npm - node -v - npm -v pre_build: commands: - echo Entring app directory - cd app - npm install - npm run lint build: commands: - echo Build started on `date` - npm run build post_build: commands: #環境変数にCloudfrontのディストリビューションを設定しておく - aws cloudfront create-invalidation --distribution-id $DISTRIBUTION --paths "/*" artifacts: files: - '**/*' #S3に入れる中身を指定 #buildするとbuildフォルダが出来るのでそれを指定 base-directory: app/build
3-2. Deploy
CodeBuildの作成が終わったら次にS3へのデプロイの設定です。
下記のように入力し、S3のバケットを指定すれば完了です。
4.おわりに
Reactに関しては2年前に少し勉強した程度で全くわかりませんが、いずれ勉強はするつもりです。
今回、ReactのBuildについて触れ、ソースのbuildとデプロイをする機会があったので備忘録的な感じで載せました。
意外と簡単でした。ローカルだとReact死ぬ程分からなかったですが、S3へのデプロイとなると何故かすんなり行きました。
【AWS】Cloudfront使用時のedge Lambdaを使ったindex.html省略
1. 使用するサービス
(AWS)
- Cloudformation
- Cloudfront
- edge Lambda
2. 概要
CloudfrontとS3を用いたWebサイトのURLがパスを区切った時にindex.htmlを指定しないと、見たいパスが表示されない現象を、Cloudfrontへのアクセスをedge Lambdaで受け取り、パスを変換してくれる構成を説明します。
3. 実装
3-1. Cloudfrontの設定
Cloudformationのテンプレート載せます。パラメータとかは適宜調整してください。
LambdaFunctionAssociationsの部分が今回の対象です。
Cloudfrontnet: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Aliases: - !Sub "${S3DomainName}.${HostedZoneName}" Enabled: true PriceClass: PriceClass_All DefaultCacheBehavior: TargetOriginId: !Sub "S3-${WebContentsBucketBaseName}-${Project}-${Stage}/*" ViewerProtocolPolicy: redirect-to-https MinTTL: 0 AllowedMethods: - HEAD - GET - OPTIONS CachedMethods: - HEAD - GET ForwardedValues: Headers: - Authorization - Origin - Access-Control-Request-Method - Access-Control-Request-Headers Cookies: Forward: none QueryString: false LambdaFunctionAssociations: - EventType: origin-request #3-2で作成するLambdaのArn:version LambdaFunctionARN: !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:urlReplaceFunction:2"
3-2. Lambdaの実装
まず、リージョンをus-east-1にします。Lambdaを通常通りに作成し、triggerにCloudfrontを選択します。すると、下記のようにedge@Lambdaにするか聞かれるので、Deployを選択します。
実装内容としてはnodejsで以下のようになります。
exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const olduri = request.uri; const newuri = olduri.replace(/\$/, '\/index.html'); request.uri = newuri; return callback(null, request); }
4. 以上で、URLのパスだけでアクセス出来るようになります。index.htmlを打ち込む必要がなくなります。
【AWS】CloudformationでAPI GatewayとLambdaでHello World
1. 使用するサービス
(AWS)
- Cloudformation
- Lambda
2. 概要
API GatewayとLambdaを使った構成を簡単にCloudformationで実装出来るテンプレートを載せます。CORSにも対応しているのでS3の静的ウェブサイトからの通信にも対応してます。
3. 実装
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Outputs the API Parameters: Project: Description: "Project Name" Default: "demo" Type: String Stage: Description: "Environment stage" Default: dev Type: String AllowedValues: [dev, staging, prod] Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Sub "${Project}-${Stage}-HelloWorldFunction" Handler: index.handler Runtime: python3.8 CodeUri: ./hello Role: !GetAtt HelloWorldRole.Arn HelloWorldRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${Project}-${Stage}-HelloWorldRole" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole MaxSessionDuration: 3600 Path: "/" HelloWorldFunctionApiPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction Principal: apigateway.amazonaws.com FunctionName: !Ref HelloWorldFunction RestApi: Type: AWS::ApiGateway::RestApi Properties: Body: info: version: '1.0' title: !Sub "${Project}-${Stage}-API" paths: /hello: get: produces: - "application/json" responses: "200": description: "200 response" schema: $ref: "#/definitions/Empty" headers: Access-Control-Allow-Origin: type: "string" x-amazon-apigateway-integration: uri: !Sub >- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations responses: default: statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Origin: "'*'" passthroughBehavior: "when_no_match" httpMethod: "POST" contentHandling: "CONVERT_TO_TEXT" type: "aws" options: consumes: - "application/json" produces: - "application/json" responses: "200": description: "200 response" schema: $ref: "#/definitions/Empty" headers: Access-Control-Allow-Origin: type: "string" Access-Control-Allow-Methods: type: "string" Access-Control-Allow-Headers: type: "string" x-amazon-apigateway-integration: responses: default: statusCode: "200" responseTemplates: application/json: | {} responseParameters: method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Origin: "'*'" requestTemplates: application/json: "{\"statusCode\": 200}" passthroughBehavior: "when_no_match" type: "mock" definitions: Empty: type: object title: Empty Schema x-amazon-apigateway-gateway-responses: UNAUTHORIZED: statusCode: 401 responseParameters: gatewayresponse.header.Access-Control-Allow-Origin: "'*'" responseTemplates: application/json: "{\"message\":$context.error.messageString}" swagger: '2.0' ApiGatewayDeploy: Type: AWS::ApiGateway::Deployment Properties: RestApiId: !Ref RestApi Description: "apigateway deployment you need to change Title when you modify Apigateway to deploy" StageName: !Ref Stage
4. おわりに
上記のテンプレートで簡単なAPI GatewayとLambdaの実装ができます。CORSの元URLの記載など、細かい部分の修正は必須ですが、とりあえず実装して試してみたいって場合には十分かと思います。
【AWS】CloudformationでSSH接続可能なEC2作成
1. 使用するサービス
(AWS)
- Cloudformation
- InternetGateway
- SecurityGroup
- EC2
2. 概要
今回は改めて基本に立ち帰り、SSHで外部接続出来るEC2の作成をCloudformationのテンプレートで説明しようかと思います。
構成は単純で、パブリックサブネットに対してVPCにアタッチされたインターネットゲートウェイにルートテーブルが設定され、かつEC2をパブリックサブネットに配置し、セキュリティグループに22番ポートを空けた物を設定して終わりです。
秘密鍵は作成済の物を使用する設定です。
3. 実装
以下、テンプレートコピペでいけるはずです。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Outputs EC2 Parameters: Project: Description: "Project Name" Type: String Default: test Stage: Description: "Environment stage" Type: String Default: dev Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 Tags: - Key: "Name" Value: !Sub "${Project}-${Stage}-Vpc" InternetSubnet: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.0.0/28 AvailabilityZone: "ap-northeast-1c" VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${Project}-${Stage}-internetgateway-subnet" InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub "${Project}-${Stage}-internetgateway" InternetRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${Project}-${Stage}-internetgateway-route" VPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway InternetSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref InternetRouteTable SubnetId: !Ref InternetSubnet InternetRoute: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 RouteTableId: !Ref InternetRouteTable GatewayId: !Ref InternetGateway DependsOn: VPCGatewayAttachment EC2SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: EC2 SecurityGroup VpcId: !Ref VPC Tags: - Key: "Name" Value: !Sub "${Project}-${Stage}-workbench" EC2SecurityGroupIngress: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref EC2SecurityGroup IpProtocol: tcp FromPort: '22' ToPort: '22' CidrIp: 0.0.0.0/0 EC2: Type: AWS::EC2::Instance Properties: DisableApiTermination: 'false' InstanceInitiatedShutdownBehavior: stop ImageId: ami-01748a72bed07727c InstanceType: t2.micro KeyName: testKey #作成済のキーペア名を入力 Monitoring: 'false' Tags: - Key: Name Value: !Sub "${Project}-${Stage}-workbench" NetworkInterfaces: - DeleteOnTermination: 'true' Description: Primary network interface DeviceIndex: 0 SubnetId: !Ref InternetSubnet GroupSet: - !Ref EC2SecurityGroup EC2EIP: Type: AWS::EC2::EIP Properties: InstanceId: !Ref EC2
4. おわりに
以上で、インターネットから接続出来るEC2の作成完了です。
何かインストールしたければ、ユーザデータにパッケージインストールの設定をすればいいと思います。
今回の用途的には外部からの踏み台として使用していて、EC2のセキュリティグループからのMYSQL接続を許可してRDSの接続等に使用していました。
【AWS】CodeBuildをVPCに入れる
1. 使用するサービス
(AWS)
- CodeBuild
- InternetGateway
- NatGateway
2.概要
CodeBuild内でRDSに接続する必要がある場合、VPC内に設置する必要があります。今回はCodeBuildのVPC内設置の仕方について説明します。
大まかな図としては以下になります。
①Internetgateway
インターネット接続のゲートウェイ。VPC自体にアタッチする。
②Nat Gateway
プライベートサブネット内の端末からインターネット接続出来るようにする為のゲートウェイ。
③CodeBuild
AWSサービスのビルドツールです。
④RDS Proxy
今回はRDSProxy->RDSの形をとっていて、実際にCodeBuildが接続する先はRDSProxyになります。
⑤RDS
接続先のデータベースサービス
3. 実装
前提として、VPCとCodebuild、各種サブネット、RDSProxy RDSは作成済とします。
3-1. InternetGatewayの作成
ネットに落ちている通りに作成して、作成済のVPCにアタッチしておわりです。
3-2. Nat Gatewayの作成
Subnetはパブリックにしたいサブネットを選択します。Elastic IPAllocate Eastic IPで新規作成します。
3-3. サブネットのルートテーブル作成
サブネットそれぞれでルートテーブルを作成する。
VPCはどちらも作成済のVPCを洗濯します。
3-4. ルートテーブルの編集をする。
作成したルートテーブルをそれぞれ編集します。
下記のRoutesからedit routesを選択します。
①パブリックサブネット側
0.0.0.0/0の転送先を作成したInternetGatewayにします。
②プライベートサブネット側
0.0.0.0/0の転送先を作成したNAT Gatewayにします。
3-5 ルートテーブルをサブネットに紐付ける
Subnet Associationsを選択し、Edit subnet associationsを開いてサブネット紐付け画面を開き、パブリック、プライベートそれぞれのサブネットに紐付けをします。
3-6. CodeBuildのVPC設定
CodeBuildの画面を開いて、editを選択してドロップダウンを表示し、environmentを選択肢、環境設定画面を開きます。
VPCの蘭があるので、作成済のVPCを選択し、サブネットにはプライベートサブネットを選択、セキュリティグループにはRDSProxyに接続可能なものを選択します。
4. おわりに
以上で、CodeBuildのVPC内設置は完了です。
CodeBuildの環境設定画面にあるValidate VPC Configボタンを押して成功すれば疎通確認完了となります。
【AWS】Cloud9上でITテスト
1. 使用するサービス
(AWS)
- Cloud9
2. 使用する言語
3.概要
まず、ここで言う結合テストの目的としては、Cloud9上の関数をAWSのサービス、Lambdaとしてデプロイした後の疎通確認として位置付けます。テストの内容も単体テストと同じです。違いとしては仮想的なLambdaか実際にデプロイしたLambdaに対してテストをするかになります。
実装面に関しては違いのある箇所のみ記載します。
後は前回参照です。
buffalokusojima.hatenablog.com
4. 実装
import unittest import json import csv import logging import os import boto3 lambda_client = boto3.client('lambda') logger = logging.getLogger() logger.setLevel(logging.INFO) def readTestData(csvFile): test_list = [] with open(csvFile, encoding="UTF-8") as f: for line in csv.DictReader(f): del line["備考"] test_list.append(line) return test_line class TestHandlerClass(unittest.TestCase): def test_response(self): logger.info("Hello Lambda Test") test_file_path = "../Data/hello.csv" test_file_path = os.path.join(os.path.dirname(__file__), test_file_path) test_list = readTestData(test_file_path) logger.info(test_list) for test in test_list: result = lambda_client.invoke(FunctionName=Project+"-"+Stage+"-HelloWorldFunction") self.assertEqual(result['statusCode'], 200) result = result['Payload'].read() result = json.loads(result) self.assertEqual(result['statusCode'], 200) self.assertEqual(json.loads(result['body']), test) self.assertEqual(result['headers'], {"Content-type": "application/json"}) if __name__ == '__mian__': unittest.main()
5. おわりに
これでデプロイ後のLambdaに対してテストが自動で行えます。
主に、単体テストでロジックの確認で、結合テストではRoleやタイムアウト時間などの確認になります。
【AWS】Cloud9上でUnitテスト
1. 使用するサービス
(AWS)
- Cloud9
2. 使用する言語
3.概要
Cloud9上でPythonのモジュール、unittestを用いたUnitテストに関して記載します。主にテストの対象はLambdaでテストの内容は基本的に与えられたテストデータとそれに対して期待通りのリターンをするかのブラックボックステストになります。
4. 実装
4-1. フォルダ構成
下記のようにhello Lambdaとそれをテストするtest_hello.py、及びテストデータとなるhello.csvを用意します。
demo----hello
| |
| |--index.py
|
|--tests
|
|--Data
| |
| |--hello.csv
|
|--Unit
|
|-test_hello.py
4-2. テスト対象のLambda
下記のようにコードを用意しておきます。
import json def handler(event, context): data = { "data": "Hello World" } print("hello world") return { "statusCode": 200, "body": json.dump(data), "headers": {"Content-type": "application/json"} }
4-3. テストスクリプト
か機能ようにtest_hello.pyのコードを用意しておきます。テストスクリプトのファイル名の最初には必ずtestをつけてください。
import unittest import json import csv import logging import os from hello import index logger = logging.getLogger() logger.setLevel(logging.INFO) def readTestData(csvFile): test_list = [] with open(csvFile, encoding="UTF-8") as f: for line in csv.DictReader(f): del line["備考"] test_list.append(line) return test_line class TestHandlerClass(unittest.TestCase): def test_response(self): logger.info("Hello Lambda Test") test_file_path = "../Data/hello.csv" test_file_path = os.path.join(os.path.dirname(__file__), test_file_path) test_list = readTestData(test_file_path) logger.info(test_list) for test in test_list: result = index.handler(None, None) self.assertEqual(result['statusCode'], 200) self.assertEqual(json.loads(result['body']), test) self.assertEqual(result['headers'], {"Content-type": "application/json"}) if __name__ == '__mian__': unittest.main()
4-4. テストデータ
下記のようにテストデータとその説明を備考として書いてあります。プログラム内でJSONに変換し、備考の部分は削除します。あくまでレビュー時に見やすくする為に存在します。
"data","備考" "Hello World", "正常値テスト"
5. 実行
demoフォルダの上のフォルダから実行します。
5-1. 環境変数の更新
下記コマンドでPythonの実行環境を変更します。
export PYTHONPATH=/home/ec2-user/environment/demo
5-2. 下記コマンドでテストを指定して実行します。
python -m unittest demo.tests.Unit.test_hello
5-3 下記コマンドでフォルダ内のtestと名のつくファイルを実行します。
python -m unittest discover demo.tests.Unit
6. おわりに
これで、テストデータを増やすことでテストを自動的に複数実行されるようになります。テストデータもcsvで比較的見やすく、lambdaのコードと一緒にレビューすることで漏れも少なくなるかと思います。また、レビューしてもらう前にはしっかりと事前に実行してテストが完了してから改めてレビューしてもらいましょう。