Docker Imageをpush

### 1. Dockerにログイン
$ sudo docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don’t have a Docker ID, head over to https://hub.docker.com to create one.
Username: ${dockarId}
Password: ${password}
※ログインすると、imageをpushできるようになる

### 2. Docker Push
$ sudo docker push ${userName}/httpd
latest: digest: sha256:102635a0a51899913a5f9e0b8aeb60a2e525e016261106d928910c7***** size: 948

${userName}はDockerIdでないとrequest is deniedとなるので、注意が必要

後は、コンテナとローカルとのファイルのマウント方法ですね。

Dockerfileの操作

### Dockerfileの作成
$ vi Dockerfile

Dockerfile

FROM centos
MAINTAINER hpscript <master@hpscript.com>
# RUN: buildするときに実行
RUN echo "now building..."
# CMD: runするときに実行
CMD ["echo", "running..."]

Dockerfileの実行
$ sudo docker build -t ${UserName}/${name} .
// sudo docker build -t hpscript/echo .

$ sudo docker images
$ sudo docker run hpscript/echo
running…

### Webサーバを立ち上げて、ブラウザで中身を確認する方法
$ sudo vi Dockerfile

FROM centos
MAINTAINER hpscript <master@hpscript.com>
# RUN: buildするときに実行
# RUN echo "now building..."
# CMD: runするときに実行
# CMD ["echo", "running..."]
RUN yum install -y httpd
ADD ./index.html /var/www/html/
EXPOSE 80
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]

$ sudo vi index.html

<html>
hello from Docker!
</html>

$ ls
Dockerfile index.html
// build
$ sudo docker build -t hpscript/httpd .
$ sudo docker run -p 8000:80 -d hpscript/httpd
393fc9fd240e2650789466b23076165cf49b62ebdbe9f1b6****

コンテナに変更を加え、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'),

再度動作確認