コンテナに変更を加え、imageを作成

## 実行中のコンテナ操作
### コンテナを実行
$ sudo docker run -d ${docker id} free -s 3
59b83e48eec642ec6d2d1443a62fff6b96b7facac648eb06f4******
※「-d」でバックグラウンドで動かす
※「-s 3」で3秒おきに実行

### ログ
$ sudo docker logs ${id}
実行中のコマンドライン
$ sudo docker attach –sig-proxy=false ${id}
※「ctl」+「c」で抜ける

### 実行中のdockerを停止
$ sudo docker stop ${id}

$ sudo docker ps
$ sudo docker attach –sig-proxy=false ${id}
You cannot attach to a stopped container, start it first

### 停止中のdockerを再開
$ sudo docker start ${id}
$ sudo docker ps

## ターミナルで入ってコンテナに変更を加える
### コンテナの中に入って操作
$ sudo docker run -i -t ${ImageID} /bin/bash
※「-i」はインタラクティブモード、「-t」はターミナルを立ち上げる

実行例

vagrant@vagrant-ubuntu-trusty-64:~$ sudo docker run -i -t 470671670cac /bin/bash[root@03471f0b8a7d /]# touch hello.txt
[root@03471f0b8a7d /]# ls
bin  etc        home  lib64       media  opt   root  sbin  sys  usr
dev  hello.txt  lib   lost+found  mnt    proc  run   srv   tmp  var
[root@03471f0b8a7d /]# exit
exit

$ sudo docker ps -a

### docker imageの作成
$ sudo docker commit ${containerId} ${userName}/${name}

実際の例

$ sudo docker commit 034 hpscript/hello
sha256:da9def7208eabab27c9ffd3b05f0e5fc3c8551585396bb06ecb*********
$ sudo docker images
$ sudo docker run -i -t hpscript/hello /bin/bash
[root@f1953ff957d6 /]# ls
bin  etc        home  lib64       media  opt   root  sbin  sys  usr
dev  hello.txt  lib   lost+found  mnt    proc  run   srv   tmp  var

Ubuntuでdocker pullからdocker runまで

### Dockerの概要
– 軽量な仮想化環境作成のツール
– コンテナを用いてアプリケーションをすばやく構築、テスト、デプロイできるソフトウェアプラットフォーム
– ubuntu上で開発されている
https://www.docker.com/

### Ubuntu/trusty64の用意
$ vagrant init ubuntu/trusty64

config.vm.network "private_network", ip: "192.168.33.10"

$ vagrant up
$ vagrant status
$ vagrant ssh

### Dockerインストール
公式ドキュメント: linux ubuntu install
1. Update the apt package index:
$ sudo apt-get update

2. Install packages to allow apt to use a repository over HTTPS:
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common

3. Add Docker’s official GPG key:
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add –

4. Use the following command to set up the stable repository. To add the nightly or test repository, add the word nightly or test (or both) after the word stable in the commands below.
$ sudo add-apt-repository \
“deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable”

INSTALL DOCKER ENGINE – COMMUNITY
1. Update the apt package index.
$ sudo apt-get update

2. Install the latest version of Docker Engine – Community and containerd, or go to the next step to install a specific version:
$ sudo apt-get install docker-ce docker-ce-cli containerd.io

3. List the versions available in your repo:
$ apt-cache madison docker-ce

4. Install a specific version using the version string from the second column
$ sudo apt-get install docker-ce=18.06.3~ce~3-0~ubuntu

### 確認
$ sudo docker –version
Docker version 18.06.3-ce, build d7080c1

docker pullしてdocker runするとimageが実行される

### dockerの操作
// imageの取得
$ sudo docker pull centos
// image一覧表示
$ sudo docker images
// imageの詳細表示
$ sudo docker inspect 470671

// dockerの実行
$ sudo docker run 47067 echo “hello world”
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused “process_linux.go:297: copying bootstrap data to pipe caused \”write init-p: broken pipe\””: unknown.

kernelが古い
https://github.com/docker/for-linux/issues/591
kernel >= 3.17

// kernelを4.4にバージョンアップする
$ sudo apt-get install –install-recommends linux-generic-lts-xenial
// reboot
$ sudo reboot

再度dockerを実行
vagrant@vagrant-ubuntu-trusty-64:~$ sudo docker run 47067 echo “hello world”
hello world

$ sudo docker ps -a -n=5

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)