HHVM/Hack

PHPは、ZendEngineというインタプリタエンジンを使って動いている

インタプリタの挙動
1. テキストファイルを読み込む(IO処理)
2. ソースコードをパースしてopcodeに変換(CPU処理)
3. opcodeを順番に実行

Opcacheを用いることで一度変換したopcodeをメモリ上に載せておき、2回目以降の処理はメモリにキャッシュされたopcodeを取り出して実行する

opcodeは中間言語の為、cpuやosなど実行環境に応じて実行される。その為、JavaやCなどのコンパイル言語よりも実行速度が遅い
c++やgolangは最初のコンパイルで機械語まで変換する

### Hip Hop Virtual Machine
PHPやHack言語をx64CPU向けにJITコンパイルするvirtual machine。つまり、Zend Engineで実行する代わりにJITコンパイルして実行する
HHVMはFacebook開発
HHVMはPHP7からは非推奨だが、Hack言語のサポート

### Hack言語
PHPに静的型付け機能を追加し、HHVMに最適化
開始するタグが < ?php ではなく < ?hhで、HTMLに埋め込む形ができない Hackは非同期機能をサポート Wikipedia, baiduなどが採用 PHPフレームワークやライブラリが使えない? -> Codeigniter, cake, laravel, Yiiなど動作する

### Hack言語の機能
Generic(型をパラメータとして与え、型だけ違って処理の内容が同じものを作るときに使う)
Map(keyとvalueをset) / Vector(順番に値を保持) / Set(重複不可) / Pair(2つの値をセット)
Enum(値を列挙)
async, awaitで並列実行
hh_client(実行前に構文チェック)

amazon linux2(vagrant) + Laravel 7.xでDuskを使うまでの手順

Laravelでseleniumを使おうと思ったら、duskというパッケージがあるらしいので、duskをインストールして使っていきます。環境は、vagrant、amazon linux2, laravel 7.2.1です。

### 1.duskインストール
$ php composer.phar require –dev laravel/dusk
– laravel/dusk v5.10.0 requires ext-zip * -> the requested PHP extension zip is missing from your system.

$ sudo yum install php-pecl-zip.x86_64
$ php composer.phar require –dev laravel/dusk

$ php artisan dusk:install
testsディレクトリにBrowserディレクトリが作られる

.env

APP_URL=http://192.168.33.10:8000

### 2.duskテスト
$ php artisan dusk:make LoginTest

/tests/Browser/LoginTest.php

public function test_can_login_successfull(){
        $this->browse(function (Browser $browser) {
            $browser->visit('/login')
                ->type('email', 'foo@hoge.com')
                ->type('password', 'hogehoge')
                ->press('Login')
                ->assertSee('this is subscriber');
        });
    }
$ php artisan dusk
1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\WebDriverCurlException: Curl error thrown for http POST to /session with params: {"capabilities":{"firstMatch":[{"browserName":"chrome","goog:chromeOptions":{"binary":"","args":["--disable-gpu","--headless","--window-size=1920,1080"]}}]},"desiredCapabilities":{"browserName":"chrome","platform":"ANY","chromeOptions":{"binary":"","args":["--disable-gpu","--headless","--window-size=1920,1080"]}}}

Failed to connect to localhost port 9515: Connection refused

ん? ChromeDriverが入っていない?

### 3.ChromeDriver インストール
$ ./vendor/laravel/dusk/bin/chromedriver-linux –verbose
./vendor/laravel/dusk/bin/chromedriver-linux: error while loading shared libraries: libX11.so.6: cannot open shared object file: No such file or directory
$ sudo yum install -y libX11
$ ./vendor/laravel/dusk/bin/chromedriver-linux –v
ChromeDriver 80.0.3987.106 (f68069574609230cf9b635cd784cfb1bf81bb53a-refs/branch-heads/3987@{#882})

$ php artisan dusk
1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\UnknownErrorException: unknown error: cannot find Chrome binary

なに、今度はChrome binary?

### 4.Chrome install
$ curl https://intoli.com/install-google-chrome.sh | bash
$ php artisan dusk

1) Tests\Browser\LoginTest::test_can_login_successfull
Facebook\WebDriver\Exception\NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"body textarea[name='email']"}
  (Session info: headless chrome=80.0.3987.149)

どういうこと? 

### 5.visitでパスを直接指定

public function test_can_login_successfull(){
        $this->browse(function (Browser $browser) {
            $browser->visit('http://192.168.33.10:8000/login')
                // ->dump();
                ->type('email', 'foo@hoge.com')
                ->type('password', 'hogehoge')
                ->press('Login')
                ->assertSee('this is subscriber');
        });

    }
$ php artisan dusk
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 2.18 seconds, Memory: 18.00 MB

OK (1 test, 1 assertion)

上手くいきました。半日かかりましたが、seleniumが何やってるかは理解できました。

Seleniumとは?

公式:selenium
=> Selenium automates browsers
=> 自動でブラウザを操作することで、Webサイトの動作テストを行うことができる(結合試験、総合試験などに活用)
=> 自動で画面キャプチャを保存できる
=> Seleniumには旧API(Selenium RC)と新しいSelenium Web Driverがある
=> Selenium WebDriverはブラウザの拡張機能やOSのネイティブ機能を利用
=> Apache License 2.0, 「Windows」「OS X」「Linux」「Solaris」などをサポート

JSON Wire ProtocolというRESTful API*に対応しており、このAPIにしたがってリクエストすることでブラウザを操作することができる

JSON Wire Protocol ↔︎ Firefox Driver | Chrome Driver | IE Driver ↔︎ ブラウザ拡張機能/OSのネイティブ機能 ↔︎ ブラウザ

### 注意点
– テストコードが冗長になる
– 非同期処理は苦手

LaravelでSeleniumを使うには?
-> Duskというテストフレームワークが含まれている
-> DuskはPHPUnitとphp-webdriver、seleniumを使うためのパッケージ

Laravel7.x、EC2でphp artisan down –allow=${ip}が効かない時

本番環境でメンテナンスモードにして、特定のIPだけ操作を許可したい場合は、

$ php artisan down --allow=${ip}

と書く。EC2で実行したところ、指定したipでもメンテナンスモードの画面に。

EC2 ELB: ELBを使って、SSL対応している

ELBを挟んでいるため、ip取得のclassにHTTP_X_FORWARDED_FORを追加する
$ sudo vi vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php

public function handle($request, Closure $next)
    {
        if ($this->app->isDownForMaintenance()) {
            $data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true);

            // 追加
            if (isset($data['allowed']) && IpUtils::checkIp($request->server->get('HTTP_X_FORWARDED_FOR'), (array) $data['allowed'])) {
                return $next($request);
            }

            if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) {
                return $next($request);
            }

            if ($this->inExceptArray($request)) {
                return $next($request);
            }

            throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']);
        }

        return $next($request);
    }

※HTTP_X_FORWARDED_FOR: リバースプロキシを挟んだアクセス元のIPを取得する

これで、メンテナンスモード時に、特定IPのみ操作できるようになります。
$ php artisan down –allow=${myIP}
$ php artisan up

EC2のmysql8.0.19でmmap(137363456 bytes) failed; errno 12

EC2でmysqlが頻繁に落ちる。
暫定的に以下を実行すると再起動できるが、1日経つとまた落ちる。

sudo touch /var/lib/mysql/mysql.sock
sudo chmod 777 /var/lib/mysql/mysql.sock
sudo service mysqld restart

cronの実行が影響しているのか? cronを止めて、PDOを手動実行するも問題なし。

ログを確認するとmmap(137363456 bytes) failed;となっている

$ sudo cat /var/log/mysqld.log
2020-03-21T20:45:21.161522Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.19) starting as process 12458
2020-03-21T20:45:21.299847Z 0 [ERROR] [MY-012681] [InnoDB] mmap(137363456 bytes) failed; errno 12
2020-03-21T20:45:21.299951Z 1 [ERROR] [MY-012956] [InnoDB] Cannot allocate memory for the buffer pool
2020-03-21T20:45:21.300002Z 1 [ERROR] [MY-012930] [InnoDB] Plugin initialization aborted with error Generic error.
2020-03-21T20:45:21.301125Z 1 [ERROR] [MY-010334] [Server] Failed to initialize DD Storage Engine
2020-03-21T20:45:21.301283Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
2020-03-21T20:45:21.301362Z 0 [ERROR] [MY-010119] [Server] Aborting
2020-03-21T20:45:21.306253Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.19)  MySQL Community Server - GPL.

innodb_buffer_pool_sizeを超えていることがわかる

mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
1 row in set (0.00 sec)

buffer_pool_sizeを256Mに変更
$ sudo vi /etc/my.cnf

[mysqld]
innodb_buffer_pool_size = 256M

$ sudo service mysqld restart
mysql> SHOW VARIABLES LIKE ‘innodb_buffer_pool_size’;
+————————-+———–+
| Variable_name | Value |
+————————-+———–+
| innodb_buffer_pool_size | 268435456 |
+————————-+———–+
1 row in set (0.02 sec)

これで2~3日様子を見よう。

minikubeでcreate deployment

$ sudo kubectl get nodes
$ sudo kubectl describe nodes ubuntu-bionic
$ sudo kubectl proxy
Starting to serve on 127.0.0.1:8001

※kubectlはクラスタのマスターノード上で実行される

$ sudo kubectl get pods -o wide
No resources found in default namespace.
$ sudo kubectl get pods -o json
{
“apiVersion”: “v1”,
“items”: [],
“kind”: “List”,
“metadata”: {
“resourceVersion”: “”,
“selfLink”: “”
}
}

### hello-minikube
// pod, deployment作成
$ kubectl run hello-minikube –image=k8s.gcr.io/echoserver:1.4 –port=8080
// get deployment
$ sudo kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
hello-minikube 0/1 1 0 2m
// serviceを使う
$ sudo kubectl expose deployment hello-minikube –type=NodePort
service/hello-minikube exposed
$ sudo minikube service hello-minikube –url
$ curl http://192.168.33.10:30835
CLIENT VALUES:
client_address=172.17.0.1
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://192.168.33.10:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 – lua: 10001

HEADERS RECEIVED:
accept=*/*
host=192.168.33.10:30835
user-agent=curl/7.58.0
BODY:
-no body in request-v

### hello-node
$ sudo kubectl create deployment hello-node –image=gcr.io/hello-minikube-zero-install/hello-node

$ sudo kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
hello-minikube 1/1 1 1 38m
hello-node 1/1 1 1 21m

$ sudo kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-minikube-75cb6dd856-8rxx7 1/1 Running 0 38m
hello-node-7676b5fb8d-hdj6d 1/1 Running 0 21m

$ kubectl expose deployment hello-node –type=LoadBalancer –port=8080
$ sudo kubectl get services
$ curl http://192.168.33.10:32720
Hello World!

kubectl createでDockerコンテナを使用してpodを作成する
Deploymentを作成するとDeployment、replicaset, Podが作られる

k8sのアーキテクチャ

マスターコンポーネントでnodeを制御している
コンテナはnodeのpodの中で実行される
minikubeではマスターとウォーカーは同じサーバだが、k8sでは別サーバ

ロードバランサの役割もk8sでやってしまうイメージか。
create deploymentでGCPからimageをpullしているけど、実務上ではDockerfileでコンテナの仕様を書いていくと思うんだが、その場合のnode、podはどうやって作成して関連付けていくのか良く解らんな。サンプルじゃなくて、実際にアプリを作らないときついか?

18.04(bionic)でminikube🐳

$ uname -a
Linux ubuntu-bionic 4.15.0-91-generic #92-Ubuntu SMP Fri Feb 28 11:09:48 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ git –version
git version 2.17.1

### kubectl install
$ sudo snap install kubectl –classic
$ kubectl version
Client Version: version.Info{Major:”1″, Minor:”17″, GitVersion:”v1.17.3″, GitCommit:”06ad960bfd03b39c8310aaf92d1e7c12ce618213″, GitTreeState:”clean”, BuildDate:”2020-02-12T13:43:46Z”, GoVersion:”go1.13.7″, Compiler:”gc”, Platform:”linux/amd64″}

### Docker install
$ sudo apt-get update
$ sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add –
$ sudo add-apt-repository “deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable”
$ sudo apt-get update
$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io
$ sudo docker –version
Docker version 19.03.8, build afacb8b7f0

### docker-compose
$ sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose –version
docker-compose version 1.21.2, build a133471

### minikube
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
$ chmod +x minikube
$ sudo cp minikube /usr/local/bin
$ sudo minikube start –vm-driver=none
$ sudo minikube start –vm-driver=none
$ sudo minikube status
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
$ sudo kubectl get nodes
NAME STATUS ROLES AGE VERSION
ubuntu-bionic Ready master 16m v1.17.3

なんか良く解らないがminikubeが動いているように見える🐳
nodeがminikubeでなく、ubuntu-bionicになってるけど、合ってるのだろうか?

ubuntu14.04(trusty)でminikubeを起動しようと思ったら

ubuntuでkubernetesを学びたい🐳
docker, docker-composeはインストール済

$ uname -a
Linux vagrant-ubuntu-trusty-64 4.4.0-148-generic #174~14.04.1-Ubuntu SMP Thu May 9 08:17:37 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
$ docker –version
Docker version 18.06.3-ce, build d7080c1

1. kubectlインストール
2. Minikubeインストール

### kubectlのインストール
k8s公式を参考にする
install-kubectl
$ sudo apt-get update && sudo apt-get install -y apt-transport-https
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add –
$ echo “deb https://apt.kubernetes.io/ kubernetes-xenial main” | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update
$ sudo apt-get install -y kubectl
$ kubectl version
Client Version: version.Info{Major:”1″, Minor:”17″, GitVersion:”v1.17.4″, GitCommit:”8d8aa39598534325ad77120c120a22b3a990b5ea”, GitTreeState:”clean”, BuildDate:”2020-03-12T21:03:42Z”, GoVersion:”go1.13.8″, Compiler:”gc”, Platform:”linux/amd64″}

### minikubeインストール
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
$ sudo chmod +x minikube
$ sudo cp minikube /usr/local/bin
$ sudo minikube start –vm-driver=none
* minikube v1.8.2 on Ubuntu 14.04 (vbox/amd64)
* Using the none driver based on user configuration

! ‘none’ driver reported an issue: exec: “systemctl”: executable file not found in $PATH
* Suggestion: Use a systemd based Linux distribution
* Documentation: https://minikube.sigs.k8s.io/docs/reference/drivers/none/

X none does not appear to be installed

あ、14.04は古いのか。サポートも終了しているみたいだし、18.04でやり直そう。。

$ vagrant ssh
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-91-generic x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

System information as of Sat Mar 21 05:59:25 UTC 2020

System load: 0.01 Processes: 109
Usage of /: 12.3% of 9.63GB Users logged in: 0
Memory usage: 16% IP address for enp0s3: 10.0.2.15
Swap usage: 0% IP address for enp0s8: 192.168.33.10

0 packages can be updated.
0 updates are security updates.

kubernetes基礎

公式サイト: kubernetes
Kuberntes Github:kubernetes / kubernetes

– 実行環境に依存しない(AWS, GCP, オンプレ)
– Docker以外のコンテナも可
– 複数ホストにコンテナをデプロイ
– サーバ台数関係なく、コンピュータ資源を最適化して複数のapplicationを起動できる(コンテナのリソースアロケーション)
– 負荷に応じてapplicationごとに自動で数を増減できる(コンテナの負荷分散)
– サーバを入れ替えることなくapplicationのデプロイが可能

masterとよばれる親機とnodeとよばれる子機サーバを用意して、kubernetesをそれぞれインストールして通信設定
ECSやGKEなどkubernetesマネージドサービスがある

k8sの構成は以下の通り
Ingress(通信内容をServiceにプロキシする)
Service(複数のPodに対して単一IPアドレスを割り当てる機能) ロードバランサのように振る舞う。ClusterIP、NodePort, LoadBalancer, ExternalNameなど
Cluster
L Node(コンテナをデプロイするための物理サーバ) k8sを管理するデーモンはNode上で実行
L Pod(デプロイの最小単位) 
L コンテナ

MiniKube:ローカルのVM内にk8sクラスタ内のコンテナを操作するためのKubernetesMasterと単一ノードのk8sクラスタを構築する

まずはminikubeで理解していくってことか?

EC2(amazon linux2)上でDockerを使う

テスト用ですぐにterminateするため、EC2のスペックは最小限にします

### Launch instance
– Amazon Linux 2 AMI (HVM), SSD Volume Type
– type: t2.nano
– subnet: public
– Auto-assign Public IP: enable
– volume: GP2 8Gib
– Security Group: HTTP 0.0.0.0/0, SSH 0.0.0.0/0

### 前準備
$ ssh ec2-user@${public ip} -i ~/.ssh/${秘密鍵}.pem

// git install
$ sudo yum update
$ sudo yum -y install gcc curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker autoconf
// 省略

### EC2にDocker.ceインストール
// Extras Libraryを見てみる
$ amazon-linux-extras
20 docker=latest enabled \
[ =17.12.1 =18.03.1 =18.06.1 =18.09.9 =stable ]
$ amazon-linux-extras info docker
docker recommends docker # yum install docker

ありますね、extrasからインストールします。
$ sudo amazon-linux-extras install docker
$ docker –version
Docker version 18.09.9-ce, build 039a7df

### Docker-compose install
$ sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose –version
docker-compose version 1.21.2, build a133471

## docker構築
### docker-compose.yml & Dockerfile
-docker-composeの構成やimageはlocal環境(ubuntu)と同じにする
-> nginx:1.13.5-alpine, node:12.13.0-alpine&php:7.3-fpm-alpine, FROM mysql:5.7.19

$ docker-compose build
ERROR: Couldn’t connect to Docker daemon at http+docker://localhost – is it running?
あれ?
$ sudo docker-compose build
sudo: docker-compose: command not found
ん?

権限をec2-userに付与する
$ sudo usermod -a -G docker ec2-user
$ cat /etc/group |grep docker
docker:x:993:ec2-user

$ docker info
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
あれ?

$ sudo service docker start
$ sudo docker info

### build & up -d
$ docker-compose build
$ docker-compose up -d
$ docker-compose ps
Name Command State Ports
———————————————————————-
app-1 docker-php-entrypoint php-fpm Up 9000/tcp
db-1 docker-entrypoint.sh mysql … Exit 1
nginx-1 nginx -g daemon off; Up 0.0.0.0:80->80/tcp
ん?
$ docker logs db-1

EC2でもswapメモリを増設する
$ 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

$ docker-compose ps
Name Command State Ports
————————————————————————-
app-1 docker-php-entrypoint php-fpm Up 9000/tcp
db-1 docker-entrypoint.sh mysql … Up 0.0.0.0:3306->3306/tcp
nginx-1 nginx -g daemon off; Up 0.0.0.0:80->80/tcp

### 動作確認
$ docker exec -it app-1 sh
/var/www/html # ls
index.html index.php
$ docker exec -it nginx-1 sh
/ # cd /var/www/html
/var/www/html
$ docker exec -it db-1 bash
root@3e44157ee4fc:/# mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.19 MySQL Community Server (GPL)

publicIPを叩く
http://**.***.***.***/
-> OK

なるほど、ローカルと全く一緒なので割と直ぐに環境構築ができます。
any application, anywhereが実感できる。
EC2の場合は冗長化が前提なので、mysqlのコンテナは必要なしでRDSに繋げるところが異なるか。vagrantが長かったせいか、商用環境にコンテナを使うのは違和感があったが、実際動かしてみると、Dockerでも別に差し支えないように見える。

インフラに変更を加えなければ、デプロイはCodeDeployで対応できそうだが、パッケージ等を入れるにしても、Dockerfileを更新するだけってのは確かに良いかも。
そして、デプロイやロードバランサまわり、オートスケーリングなどをkubernetesで管理するのね。