CodeDeployでBlockTrafficとAllowTrafficに5分以上かかる時

CodeDeployでBlockTrafficとAllowTrafficに5分以上かかる時

ALBのHealth checkが原因とのこと
defaultでTimeout 5 seconds, Interval 30 secondsとなっているので、ELB Targets Groupsから、Health Checkの設定をそれぞれ、2秒、5秒に変更する

AWSのフォーラムでも回答されています。
Developer Forum

Yes CodeDeploy depends on the ELB Health Check settings that you have configured your ELB with. After an instance is bound to the ELB, CodeDeploy wait for the status if the instance to be healthy ("inService") behind the load balancer. This health check is done by ELB and depends on the health check configuration you have set.

AWS ALB常時SSL(HTTPS)のLaravel 6.x Middleware書き方

つらつらと書いていますが、仕様としては、middlewareを作成後、「### 5.Middleware(ForceHttpProtocol.php)にformatSchemeをoverrideする」に飛んで頂いて構いません。

# 背景
ACMで証明書を取得後、ALBでattachし、挙動テストすると、「完全には保護されていません」と表示される
->PRDのソースを確認すると、プロトコルがhttpで出力されている

<link rel="stylesheet" href="http://${domain}/css/main.css">

ソースコードではasset()で記載

<link rel="stylesheet" href="{{ asset('css/main.css') }}">

原因は、assetが、httpsではなく、httpを読みにいっているから。

# 要件
- 商用環境でhttp通信のリクエストの場合は、https通信にリダイレクト
- ただし、ローカル環境はhttp通信
- 商用環境リンクをフォームを含めて全てhttpsに変更

## 前準備
middlewareの追加
$ php artisan make:middleware ForceHttpProtocol

app/Http/kernel.php

protected $middleware = [
        //省略
        \App\Http\Middleware\ForceHttpProtocol::class,
    ];

### 1. secure()に書き換えるやり方
app/Http/Middleware/ForceHttpProtocol.php

public function handle($request, Closure $next)
    {
        if(!$request->secure() && env('APP_ENV') === 'production'){
            return redirect()->secure($request->getRequestUri());
        }
        return $next($request);
    }

上記では永久リダイレクトとなり、タイムアウトになる。

### 2. ‘https://’.$_SERVER[‘HTTP_HOST’].$_SERVER[‘REQUEST_URI’]にリダイレクト

public function handle($request, Closure $next)
    {
        if(!$this->is_ssl() && config('app.env') === 'production'){
            return redirect('https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
        }
        return $next($request);
    }

    public function is_ssl()
    {
        if ( isset($_SERVER['HTTPS']) === true )
        {
            return ( $_SERVER['HTTPS'] === 'on' or $_SERVER['HTTPS'] === '1' );
        }
        elseif ( isset($_SERVER['SSL']) === true )
        {
            return ( $_SERVER['SSL'] === 'on' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true )
        {
            return ( strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PORT']) === true )
        {
            return ( $_SERVER['HTTP_X_FORWARDED_PORT'] === '443' );
        }
        elseif ( isset($_SERVER['SERVER_PORT']) === true )
        {
            return ( $_SERVER['SERVER_PORT'] === '443' );
        }

        return false;
    }

これでhttpリクエストの場合も、httpsにリダイレクトされるようになる。
ただし、asset、routeなどのリンクが、httpsではなく、httpを読みにいく事象は解消されないまま

->urlのhelperを確認
Illuminate/Foundation/helper.php

function url($path = null, $parameters = [], $secure = null)

URLやViewのasset, redirectを全て書き換える?
嘘だろ?

### 3. パスをassetからurlに書き換える
AppServiceProvicerで商用ならtrue, 商用以外ならfalseのメソッドを作成

app/Providers/AppServiceProvider.php

use View;
public function boot()
    {
       $is_production = env('APP_ENV') === 'production' ? true: false;
       View::share('is_production', $is_production);
    }

続いて、viewで、assetをurlに変更して、上記の変数を第3パラメータに追加
resources/view/auth/login.blad.php

<form method="POST" action="{{ url('login', null, $is_production) }}">

一箇所修正して挙動確認。
URLがhttpsに変わっていることを確認
3~4ページぐらい修正を繰り返していると、早くも絶望的な気分になってくる

### 4. Laravelコレクティブ&Controllerでのhttpsパラメータの渡し方を確認
laravel collective

{!! Form::open(['method'=>'POST', 'action'=>['HogeController@confirm',null,$is_production] ]) !!}

controller

protected $is_production;

    public function __construct(){
        $this->is_production = env('APP_ENV') === 'production' ? true: false;
    }

// 戻るボタン処理
        if($action == '戻る'){
            return redirect()->action('HogeController@create', 302, null, $this->is_production)->withInput($inputs);
        }

うまくいかない。

縷々調査すると、ssetやrouteのプロトコルはformatSchemeで生成しているとのこと。
Illuminate/Routing/UrlGenerator.php

public function formatScheme($secure = null)
    {
        if (! is_null($secure)) {
            return $secure ? 'https://' : 'http://';
        }

        if (is_null($this->cachedScheme)) {
            $this->cachedScheme = $this->forceScheme ?: $this->request->getScheme().'://';
        }

        return $this->cachedScheme;
    }

### 5.Middleware(ForceHttpProtocol.php)にformatSchemeをoverrideする

use URL;
public function handle($request, Closure $next)
    {
        if(!$this->is_ssl() && config('app.env') === 'production'){
            return redirect('https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
        }
        if (env('APP_ENV') === 'production')
        {
            URL::forceScheme('https');
        }

        return $next($request);
    }

    public function is_ssl()
    {
        if ( isset($_SERVER['HTTPS']) === true )
        {
            return ( $_SERVER['HTTPS'] === 'on' or $_SERVER['HTTPS'] === '1' );
        }
        elseif ( isset($_SERVER['SSL']) === true )
        {
            return ( $_SERVER['SSL'] === 'on' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true )
        {
            return ( strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PORT']) === true )
        {
            return ( $_SERVER['HTTP_X_FORWARDED_PORT'] === '443' );
        }
        elseif ( isset($_SERVER['SERVER_PORT']) === true )
        {
            return ( $_SERVER['SERVER_PORT'] === '443' );
        }

        return false;
    }

このように書くことで、要件を満たすことができる
- 商用環境でhttp通信のリクエストの場合は、https通信にリダイレクト
- ただし、ローカル環境はhttp通信
- 商用環境リンクをフォームを含めて全てhttpsに変更

EBSのIOPS

### GP2(General Purpose)
– HDD: SSD
– 容量: 1GiB~16TiB
– ISOP: 1TiB以下の場合、3000IOPSまでバースト

### PIOPS(プロビジョンドIOSP)
– HDD: SSD
– 容量: 10GiB~16TiB
– ISOP: 最大20,000IOPS

### Magnetic
– HDD: 磁気ディスク
– 容量: 1GiB~1TiB
– ISOP: 最大数百IOPS

秒間アクセスがどれくらい集中するかを考慮して、検討する必要がある。

laravel6.xでSESの設定

### SES
Service -> Customer Engagement -> Simple Email Service
Asia Pacific (Mumbai)
左メニューのEmailAddresses -> Verify a New Email Address -> 認証
以下のような文言が表示される

Amazon SES の新規ユーザーの場合–上限緩和をまだ申請していない場合は、引き続きサンドボックス環境を使用しています。そのため、メールは確認済みのアドレスにのみ送信できます。新しいメールアドレスまたはドメインを確認するには、Amazon SES コンソールの [Identity Management] のセクションを参照してください。

Amazon Pinpoint の新規ユーザーの場合–上限緩和をまだ申請していない場合は、引き続きサンドボックス環境を使用しています。そのため、メールは確認済みのアドレスにのみ送信できます。新しいメールアドレスまたはドメインを確認するには、Amazon Pinpoint コンソールの [Settings] > [Channels] ページを参照してください。

なお、送信制限解除申請は、AWS Support -> Create Case -> Service limit increaseから送信する

### AmazonSESFullAccessのユーザ追加
IAMからAdd user
User name:${appName}-ses
Select AWS access type: Programmatic access
Permissions: AmazonSESFullAccess

### .env

MAIL_DRIVER=ses
MAIL_FROM_ADDRESS=${ses認証のメールアドレス}

SES_KEY=
SES_SECRET=
SES_REGION=ap-south-1 ※munbai

### テスト実行
Error executing “SendRawEmail” on “https://email.ap-northeast-1.amazonaws.com”; AWS HTTP error: cURL error 6: Could not resolve host: email.ap-northeast-1.amazonaws.com (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)

あれ?
$ curl https://email.ap-northeast-1.amazonaws.com
curl: (6) Could not resolve host: email.ap-northeast-1.amazonaws.com

SES_REGIONをap-south-1に設定しているのに、ap-northeast-1にリクエストを送ってます。
configでsesの設定を確認してみます。

config/services.php

'ses' => [
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    ],

regionがAWS_DEFAULT_REGIONとなっており、さらに、keyとsecretも共通のkey, secret担っているので、AWS_DEFAULT_REFIONをmunbaiに変更します。

'ses' => [
        'key' => env('SES_KEY'),
        'secret' => env('SES_SECRET'),
        'region' => env('ap-south-1', 'us-east-1'),
    ],

$ php artisan config:clear

同時に、mailのfromを.envから取得するように変更します。

->from(env('MAIL_FROM_ADDRESS'))

SESで送信できていることが確認できます。

EC2をprivate subnetに移し、SSH用のbastionサーバの構築

### 既存のWebサーバのAMIを取得
Instances -> Actions -> Image -> Create Image

### purivate subnetのインスタンスの作成
作成したAMIから、再度インスタンスを作る
${appName}-prd-01-private
vpc:dev-vpc
subnet:dev-subnet-private1
Public IP:enable
IAM role: s3readonly
Security group: prd

${appName}-prd-02-private
vpc:dev-vpc
subnet:dev-subnet-private2
Public IP:enable
IAM role: s3readonly
Security group: prd

### public subnetのインスタンス削除
instance -> instance state -> terminate

### ALBのターゲットグループに追加
TargetGroup -> Target -> 追加
->ALBのDNSを叩いて動作確認
->コンソールからinstanceのpublic ipを叩いてssh接続できない事を確認

### bastionのサーバ構築
Instance Type: t2.nano
VPC: dev-vpc
subnet: dev-public-subnet
Auto-assign Public IP: enable
storage size: 8Gib
tag: ${appName}-prd-bastion
security group: create ${appName}-sg-bastion, Custom TCP
key pair: 新規推奨

### bastionのkey pair
bastionサーバにログインし、home/ec2-user/.sshに 秘密鍵(***.pem)を配置する
bastionにSSHログイン

$ ssh ec2-user@*** -i  ~/.ssh/***.pem
$ sudo chmod 600 /home/ec2-user/.ssh/***.pem
$ cd .ssh
$ ls -l

### Webサーバのセキュリティグループ
ssh接続のソースをmyIPに変更

### bastionからprivate subnetのweb serverにログイン
$ ssh ec2-user@${private_ip} -i .ssh/aws-dev.pem

$ curl https://www.google.co.jp/
-> timeout
-> private subnetは、インターネットゲートウェイのルーティングが存在しないことが原因

### NATゲートウェイ作成
VPC -> NATGateways -> create
subnet: dev-subnet-public1
Elastic IP: 割り当て

Private route table
NAT Gateway 割り当て

curl https://www.google.co.jp/
-> レスポンスが返るようになる。

laravel6.x : ELB使用時にElastiCache(Redis)でセッション管理する

AWSのELBで冗長化する場合、TokenMismatchExceptionが発生しないよう、セッション管理をデフォルトのCookieからRedisに変更する。
ALBでStickinessを有効化する事で、Cookieのままでも機能的にはおよそ問題ないとも言えるが、
「インスタンスが死んだ場合にはセッションが切れる」「負荷分散が偏る場合が発生する」とのことで、
要件次第のところもあるが、基本的にはスティッキークッキーではなく、ElastiCacheでの管理に変更する。

### 1.Redis用のsubnet作成
Services->Database->ElastiCache->SubnetGroup
Name:${appName}-redis-subnet-group
Description:${appName}-redis-subnet-group
VPC:dev-vpc
Subnet:dev-subnet-private1(ap-northeast-1a)、dev-subnet-private2(ap-northeast-1c)

### 2.Redis用のSecurityGroup作成
name:redis-security-group
description:redis-security-group
VPC:dev-vpc

Edit Inbound Rule
Custom TCP, 6379port, PRDのSecurityGroup

### 3.Redisの作成
Name: ${appName}-redis
Engine version compatibility: 5.0.6
Port:6379
Parameter group:default.redis5.0
nodetype: t2.medium
replica:0
subnet group: ${appName}-redis-subnet-group
security group: redis-security-group
backup: null

### 4. Laravelでredisを使えるようにする
$ ssh ec2-user@*** -i ~/.ssh/***.pem
$ cd /var/www/${appName}

// predisを入れる為、メモリ拡張
$ sudo /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024
$ sudo /sbin/mkswap /var/swap.1
$ sudo /sbin/swapon /var/swap.1
$ free

$ sudo php composer.phar require predis/predis

$ sudo vi .env

CACHE_DRIVER=redis
SESSION_DRIVER=redis
REDIS_HOST=${redis endpoint}

$ php artisan config:clear
$ chown apache:apache /var/www/${appName}/storage/logs/laravel.log

で、動作確認すると、

please make sure the php redis extension is installed and enabled

何いいいいいいいいいいいいいい
「sudo yum install php-redis」でphp-redis入れろって事?

あ、
config/database/php
122行目
phpredis -> predis

'redis' => [

        'client' => env('REDIS_CLIENT', 'predis'),

再度動作確認

ELBで異なるsubnetのEC2に冗長化する書き方

ELBで負荷分散して冗長化する

### public subnetを二つ用意
– availability zoneが異なるpublic subnetの中に、それぞれEC2を配置する
現状

dev-vpc(192.168.0.0/16)
dev-subnet-public1(192.168.1.0/28) ap-northeast-1a ・・・web-prd-01 
dev-subnet-private1(192.168.2.0/28) ap-northeast-1a ・・・RDS MultiAZ
dev-subnet-private2(192.168.3.0/28) ap-northeast-1c・・・RDS MultiAZ 

上記を踏まえ、以下のsubnetを作成する
1.dev-subnet-public2(192.168.4.0/28) ap-northeast-1c ・・・web-prd-02
2.作成済みのpublic route tableを関連付ける(internet gatewayは紐付け済)

### AMIからインスタンス作成
– web-prd-01のAMIからインスタンスを複製する
– VPCにdev-vpc、subnetにdev-subnet-public2を割り当てる
– Auto-assign Public IP Enable, IAM role s3readonly

-インスタンスを起動し、動作確認
-SSHログインしてファイルも確認する

### ロードバランサの構築
– EC2左メニューLOAD BALANCINGのTarget Groupsの作成
– target group name: ${app name}-target-group
– target type: instance
– Protocol: HTTP
– Port: 80
– VPC:dev-vpc
– Health check settings: http, /

作成したtarget-groupにAction-> Register and deregister instance/ip targets からweb-prd-01, web-prd-02を紐付ける

### ALBの作成
Name: ${appName}-prd-alb
Scheme: internet-facing
IP address type: ipv4
Lisnter: http:80
Availability Zones:
VPC: dev-vpc
Subnet: dev-subnet-public1, dev-subnet-public2

Configure Security Groups
-> ALBのSecurity Groupを作成する
-> alb-${appName}-security-group
-> Custom TCP

Configure Routing
– Target group: Existing target group
– Name: ${appName}-target-group
– Protocol Port: HTTP 80

### security groupを新規に作成
インバウンドルールに、ALBのみから受け付けるように設定する
– SSH:0.0.0.0/0
– HTTP: ${albのsecuritygroup ID}
インスタンス(web-prd-01, web-prd-02)のセキュリティグループを新規に作成したセキュリティグループに変更

->ALBのDNS nameを叩いて動作確認
->instanceのpublic ipを叩いてもresponseが返ってこない事を確認

MultiAZのRDSを作成し、EC2からRDSに接続する方法

RDS用にavailability zoneが異なる二つのsubnetと、InternetGatewayに接続しないroute table, RDS用のsecuirtyグループを作成して、EC2と同じVPCの中にRDSを作成する。

## Private subnetの作成
privateなsubnetを二つ作成する。その際に、subnetのAvailability Zoneは別々にする。VPCは同じ

192.168.2.0/28(dev-subnet-private1)
 L ap-northeast-1a
192.168.3.0/28(dev-subnet-private2)
 L ap-northeast-1c

※以前作成したpublicなsubnet

192.168.1.0/28(dev-subnet-public) 
 L ap-northeast-1a

## Private route tableの作成
name:private-routetable
Private route tableにPrivate Subnetを紐付け

## RDS用のSubnet group作成
Services -> RDS -> Subnet group
name: ${appname}-subnet-group
vpc: 同じ
add subnet: private subnetを追加

192.168.2.0/28(dev-subnet-private1)
 L ap-northeast-1a
192.168.3.0/28(dev-subnet-private2)
 L ap-northeast-1c

## RDS用のSecurity group作成
name: rds-security-group
inbound rules:
Type:MYSQL/Aurora
Protocol: TCP
Port Range: 3306
Source: dev-security-group(ec2のセキュリティグループ)

### DB作成
MySQL
8.0.17(最新)
db.t.micro
General Purpose(SSD)
20 GiB
Subnet group: ${appname}-subnet-group
Security Group:rds-security-group

### EC2からDBインスタンスにログイン
1. EC2にssh
ssh ec2-user@*** -i ~/.ssh/***.pem

2. /.sshにrds-ca-2019-root.pemを配置

3. EC2からRDSにssh接続
mysql -h ${endpoint}.ap-northeast-1.rds.amazonaws.com –ssl-ca=.ssh/rds-ca-2019-root.pem -u root -p

Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 16
Server version: 8.0.17 Source distribution

Copyright (c) 2000, 2020, 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>

この後.envファイルを編集します。

DB_CONNECTION=mysql
DB_HOST=${endpoint}.ap-northeast-1.rds.amazonaws.com
DB_PORT=3306
DB_DATABASE=${db_name}
DB_USERNAME=hoge
DB_PASSWORD=hogehoge

migrateして、上手くいけば、ec2からMySQLに入り、初期データを入れる為、クエリを実行します。
// 省略
Query OK, 1 row affected (0.01 sec)

ansible-playbookでvagrantからEC2にデプロイする方法

[EC2側]
まず、EC2を起動し、ターミナルからssh接続
$ ssh ec2-user@**** -i ~/.ssh/***.pem
$ cd /home/release
$ ls
LICENSE appspec.yml index.html scripts

[vagrant側]
.ssh/、inventory/hostsを作成済み
/hosts

[targets]
**.***.**.**

今回デプロイするtest.htmlを作成する

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	ansible deploy test
</body>
</html>

$ ls
LICENSE appspec.yml index.html inventory playbook.yml scripts test.html

playbook.yml

- hosts: targets
  sudo: yes
  tasks:
    - name: put test.html
      copy: src=test.html dest=/home/release/ owner=root group=root mode=640

### デプロイ実行
$ ansible-playbook -i inventory/hosts –private-key=.ssh/***.pem playbook.yml -u ec2-user
PLAY [targets] *****************************************************************

TASK [Gathering Facts] *********************************************************
ok: [**.***.**.**]

TASK [put test.html] ***********************************************************
changed: [**.***.**.**]

PLAY RECAP *********************************************************************
**.***.**.** : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

### EC2側で確認
$ ls
LICENSE appspec.yml index.html scripts test.html
$ sudo cat test.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	ansible deploy test
</body>
</html>

playbook.ymlは、デプロイする場合は、copy: src=${source file} dest=${dest directory} owner=root group=root mode=640 でOK
test.htmlを修正して、再度ansible-playbookを叩いた場合でも、EC2側で反映されていることが確認できます。

ansibleでvagrantからEC2にSSH接続するテスト

Githubではなく、ローカルからEC2にデプロイしたい時もあるでしょう。
という事で、AnsibleからEC2にSSH接続する方法を確認していきます。

### 前提
– vagrantにansibleインストール済

$ ansible –version
ansible 2.8.5

### .gitignore
git pushする際に、秘密鍵がレポジトリにpushされないよう、gitignoreを設定します。
.gitignore

/.ssh

.ssh フォルダにテストファイルを作成して.gitignoreが動くかテスト
$ git add .
$ git commit -m “gitignore added”
$ git push -u origin master

レポジトリにて、.sshがpushされていない事を確認

## -m pingテスト
### .ssh
ec2の秘密鍵を.sshに配置
$ chmod 700 .ssh/

### EC2
instanceからEC2をstart

### host
inventory/hosts
-> instanceのpublic ip(テスト用)を入れる

[targets]
**.***.**.**

### ansible command
$ ansible all -i inventory/hosts -m ping

**.***.**.** | UNREACHABLE! => {
    "changed": false, 
    "msg": "Failed to connect to the host via ssh: Warning: Permanently added '**.***.**.**' (ECDSA) to the list of known hosts.\r\nPermission denied (publickey,gssapi-keyex,gssapi-with-mic).", 
    "unreachable": true
}

鍵認証が上手くいっていない。

$ ansible all -i inventory/hosts –private-key=.ssh/***.pem -m ping -u ec2-user

**.***.**.** | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}