takaya030の備忘録

PHP、Laravel、Docker などの話がメインです

Docker で Ruby on Rails5 の開発環境を構築する

Docker で Ruby on Rails5 の開発環境のベースイメージを作成したときの手順メモ

設定ファイルおよび操作手順について

こちらのサイトに記載されている手順で rails5 もセットアップできました。
qiita.com


以下、内容を変更した設定ファイルです。

Dockerfile

#
# ruby 2.3 + rails 5.0.0.1
#
# 2016-10-05
#

FROM ruby:2.3
MAINTAINER takaya030

ENV APP_ROOT /usr/src/myapp

WORKDIR $APP_ROOT

RUN apt-get update && \
    apt-get install -y nodejs \
                       mysql-client \
                       postgresql-client \
                       sqlite3 \
                       --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT

RUN \
  echo 'gem: --no-document' >> ~/.gemrc && \
  cp ~/.gemrc /etc/gemrc && \
  chmod uog+r /etc/gemrc && \
  bundle config --global build.nokogiri --use-system-libraries && \
  bundle config --global jobs 4 && \
  bundle install && \
  rm -rf ~/.gem

# bundle install でカレントディレクトリに rails5 をインストールした後、
# 以下をコメントアウトして再度 docker build を実行する

#COPY . $APP_ROOT
#
#EXPOSE  3000
#CMD ["rails", "server", "-b", "0.0.0.0"]

Gemfile

source "https://rubygems.org"
gem 'rails', '5.0.0.1'

docker-compose.yml

version: '2'
services:
  app:
    build: .
    image: developer_name/project_name
    container_name: project_name_app
    environment:
      RAILS_ENV: development
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/usr/src/myapp
    ports:
      - "3000:3000"

Docker で nginx + php-fpm + Laravel5.3 の開発環境を作る

DockerLaravel 5.3 の開発環境を構築した際の手順メモ

検証環境

以下の環境で検証しました
VirtualBoxDocker はインストール済みの前提で話を進めます

Windows10 Home Edition
VirtualBox 5.0.16
docker 1.10.3
docker-machine 0.6.0
docker-compose 1.6.2

準備作業

Windowsのエディタで php のコードを編集した結果を直ちに反映させるため、Windows のフォルダを VirtualBox にマウントし、そこに Laravel をインストールします
マウント手順は下記の記事を参照してください
takaya030.hatenablog.com

C:\workspace をマウントした後、C:\workspace\laravel53 フォルダを作成します

Docker イメージの作成のための各種ファイル

ファイル構成

l53
│  docker-compose.yml
│
├─nginx
│      Dockerfile
│      server.conf
│
└─php7
        bashrc
        Dockerfile
        index.php
        mysupervisord.conf

各ファイルの内容

l53/docker-compose.yml
nginx: 
  build: ./nginx
  ports: 
    - "80:80"
    - "9000:9000"
  links: 
    - php7

php7: 
  build: ./php7
  hostname: laraweb
  ports: 
    - "2022:22"
  volumes: 
    - /workspace/laravel53:/webapp
l53/nginx/Dockerfile
FROM nginx:latest
MAINTAINER takaya030

ADD server.conf /etc/nginx/conf.d/server.conf
l53/nginx/server.conf
server {
    listen 80 default;
    server_name _;
    root /webapp/public;
    index index.php index.html index.htm;
    charset utf-8;

    access_log off;
    error_log off;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ ^/index.php$ {
        fastcgi_pass l53_php7_1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}

server {
    listen 9000 default;
    server_name _;
    root /var/www/html;
    index index.php index.html index.htm;
    charset utf-8;

    access_log off;
    error_log off;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ ^/index.php$ {
        fastcgi_pass l53_php7_1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}
l53/php7/bashrc
# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

# git prompt
if [ -f $HOME/.git-completion.bash ]; then
	source $HOME/.git-completion.bash
fi
if [ -f $HOME/.git-prompt.sh ]; then
	source $HOME/.git-prompt.sh
	export PS1='\[\033]0;$TITLEPREFIX:${PWD//[^[:ascii:]]/?}\007\]\n\[\033[32m\]\u@\h\[\033[35m\]: \[\033[33m\]\w\[\033[36m\]`__git_ps1`\[\033[0m\]\n$ '
fi

# User specific aliases and functions
alias ls='ls -F --color=auto'
alias ll='ls -la --color=auto'
alias la='ls -a --color=auto'
alias sl='ls -F --color=auto'
alias glog='git log --oneline --decorate --graph --branches --tags --remotes'
l53/php7/Dockerfile
#
# php7-fpm
#
# 2016-09-18
#   PHP 7.0.11

FROM php:7-fpm
MAINTAINER takaya030

# php
RUN apt-get update \
  && apt-get install -y libfreetype6-dev libjpeg62-turbo-dev libpng12-dev libmcrypt-dev \
  && docker-php-ext-install pdo_mysql mysqli mbstring gd iconv mcrypt \
  && apt-get clean

# sshd, scp, sudo, unzip, tar
RUN apt-get install -y openssh-server openssh-client sudo tar unzip supervisor && \
    apt-get clean

# gcc (for building git)
RUN apt-get install -y build-essential libssl-dev gettext curl expat openssl zlibc libcurl4-openssl-dev \
	&& apt-get clean

# git
RUN curl -O https://www.kernel.org/pub/software/scm/git/git-2.9.3.tar.gz && \
	tar xvzf git-2.9.3.tar.gz && \
	cd git-2.9.3 && \
	make configure && \
	./configure --prefix=/usr/local --with-curl --with-expat && \
	make all && \
	make install && \
	cd .. && \
	rm -r git-2.9.3 git-2.9.3.tar.gz

# for git prompt
RUN curl -L https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash > /etc/skel/.git-completion.bash && \
	curl -L https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh > /etc/skel/.git-prompt.sh 
ADD ./bashrc /etc/skel/.bashrc

# composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# phpunit
RUN curl -L https://phar.phpunit.de/phpunit.phar > /usr/local/bin/phpunit && \
	chmod +x /usr/local/bin/phpunit

# node, npm, gulp
RUN apt-get install -y nodejs npm && \
	npm cache clean && \
	npm install n -g && \
	n stable && \
	apt-get purge -y nodejs npm && \
	apt-get clean && \
	/usr/local/bin/npm install --global gulp-cli

# initialize for ssh
RUN sed -i '/pam_loginuid\.so/s/required/optional/' /etc/pam.d/sshd && \
	/usr/sbin/service ssh start && \
	/usr/sbin/service ssh stop

# create login user
RUN useradd -d /home/laravel -m -s /bin/bash laravel && \
	echo laravel:****laravel | chpasswd && \
	echo 'laravel ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# timezone
RUN cp -p /usr/share/zoneinfo/Japan /etc/localtime && \
	echo "date.timezone = Asia/Tokyo" > /usr/local/etc/php/conf.d/myphp.ini

# supervisor
COPY ./mysupervisord.conf /etc/supervisor/conf.d/

COPY index.php /var/www/html/

EXPOSE 22 9000

CMD ["/usr/bin/supervisord"]
l53/php7/index.php
<?php
	phpinfo();
l53/php7/mysupervisord.conf
[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)
nodaemon=true               ; (start in foreground if true;default false)

[program:sshd]
command=/usr/sbin/sshd -D

[program:php-fpm]
command=/usr/local/sbin/php-fpm

Docker イメージの作成

Docker ホストに上記のファイルを配置した後、以下のコマンドでイメージを作成します

$ cd l53
$ docker-compose build

コンテナの起動

$ cd l53
$ docker-compose up -d

ssh でログイン

$ ssh -p 2022 laravel@192.168.99.100

※パスワードは "****laravel"

Laravel 5.3 のインストール

ssh でログインしたコンテナ上で以下のコマンドを実行する

$ cd /webapp
$ composer create-project "laravel/laravel" --prefer-dist .

動作確認

WEBブラウザで http://192.168.99.100 にアクセスすると Laravel の welcome ページが表示されます
f:id:takaya030:20160920230537p:plain

また http://192.168.99.100:9000 にアクセスすると phpinfo が表示されます
f:id:takaya030:20160920230600p:plain

更新履歴

  • (2016-09-27) l53/php7/Dockerfilenode, npm, gulp のインストールを追加

oriceon/oauth-5-laravel を使った Google API の OAuth 認証

前回手動で Google API の Access Token と Refresh Token を取得したので、それを使って oriceon/oauth-5-laravel で OAuth 認証を検証したときのメモ

検証環境

$ php --version
PHP 5.5.19 (cli) (built: Nov 12 2014 12:35:44)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.4-dev, Copyright (c) 1999-2014, by Zend Technologies

$ php artisan --version
Laravel Framework version 5.2.45

oriceon/oauth-5-laravel のインストール

composer.json に下記を追加

    "require": {
	"oriceon/oauth-5-laravel": "dev-master"
    },

composer update を実行

$ composer update

OAuth クライアント ID、Refresh Token の取得

前回の記事を参考に予め取得する
Google API の Access Token を手動で取得する - takaya030の備忘録

サンプルコード

.env に下記を追加

# oauth
GOOGLE_CLIENT_ID=Your-Clinet-Id
GOOGLE_CLIENT_SECRET=Your-Clinet-Secret
GOOGLE_ACCESS_TOKEN=Your-Access-Token
GOOGLE_REFRESH_TOKEN=Your-Refresh-Token

config/oauth-5-laravel.php

<?php

return [

	/*
	|--------------------------------------------------------------------------
	| oAuth Config
	|--------------------------------------------------------------------------
	*/

	/**
	 * Storage
	 */
	'storage' => '\\OAuth\\Common\\Storage\\Session',

	/**
	 * Consumers
	 */
	'consumers' => [

		'Google' => [
			'client_id'     => env('GOOGLE_CLIENT_ID'),
			'client_secret' => env('GOOGLE_CLIENT_SECRET'),
			'scope'         => ['https://mail.google.com/'],
		],

	]

];

app/Http/Controllers/GmailController.php

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

use OAuth\OAuth2\Token\StdOAuth2Token;

class GmailController extends Controller
{
	public function loginOAuth(Request $request)
	{
		// get data from request
		$access_token = env("GOOGLE_ACCESS_TOKEN");
		$refresh_token = env("GOOGLE_REFRESH_TOKEN");

		// get google service
		$googleService = \OAuth::consumer('Google');

		if( !is_null($access_token) && !is_null($refresh_token) )
		{
			$dummy_token = new StdOAuth2Token( $access_token, $refresh_token );
			$token = $googleService->refreshAccessToken($dummy_token);

			$params = [
				'maxResults' => '3',
				'q' => 'is:inbox',
			];

			// Send a request with it
			$result = json_decode($googleService->request('https://www.googleapis.com/gmail/v1/users/me/messages?'.http_build_query($params),'GET'), true);

			dd($result);
		}
		else
		{
			dd('No Access Token or Refresh Token.');
		}
	}

}

app/Http/routes.php

<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/

Route::get('gmail', 'GmailController@loginOAuth' );

autoload 更新

$ php artisan optimize

動作確認

テストサーバー起動

$ php artisan serve

WEBブラウザで http://localhost:8000/gmail を開いて下の画像のように表示されれば成功です
f:id:takaya030:20160904174005p:plain

Google API の Access Token を手動で取得する

はじめに

Google App Engine (GAE) を運用する場合通常サービスアカウントを使用しますが、サービスアカウントから個人の Gmail などにアクセスする際には別途 OAuth 認証を行って Access Token、Refresh Token を取得する必要があるため、その手順のメモ

OAuth クライアント ID の取得

  1. 認証を取得したい Google アカウントにログイン
  2. Google Developers Console に移動
  3. API Manager でプロジェクトを作成 or 選択、使用する API を有効にする
  4. API Manager の"認証情報"で、"認証情報を作成" → "OAuth クライアント ID" → "その他" の順で選択して作成
  5. 作成した OAuth クライアント ID を "JSONをダウンロード" してクライアント ID、クライアントシークレット、リダイレクトURIを確認する

Authorization Code 取得

Webブラウザで下記 URL にアクセスする

https://accounts.google.com/o/oauth2/auth?client_id=12345-xxxxx.apps.googleusercontent.com&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://mail.google.com/&response_type=code&approval_prompt=force&access_type=offline

client_id は上で取得したもの
scope は例として Gmail API のものを指定している。他の API を使う場合はに適宜変更すること
ブラウザに認証コードが表示されるのでメモする

scope を複数指定する場合

下記のように %20 で区切って指定する

scope=https://mail.google.com/%20https://www.googleapis.com/auth/datastore

Access Token 取得

上で取得した Authorization Code を使って Access Token を取得する (code で指定)

curl -k -d client_id=12345-xxxxx.apps.googleusercontent.com -d client_secret=xxxxx -d redirect_uri=urn:ietf:wg:oauth:2.0:oob -d grant_type=authorization_code -d code=xxxxx https://accounts.google.com/o/oauth2/token

下記JSONがレスポンスで返されるので access_token、refresh_token をメモする

{
  "access_token" : "xxxxx...",
  "token_type" : "Bearer",
  "expires_in" : 3600,
  "refresh_token" : "xxxxx..."
}

oriceon/oauth-5-laravel を使った Pocket API の OAuth 認証

oriceon/oauth-5-laravel で Pocket API の OAuth 認証を検証したときのメモ

検証環境

$ php --version
PHP 5.5.19 (cli) (built: Nov 12 2014 12:35:44)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.4-dev, Copyright (c) 1999-2014, by Zend Technologies

$ php artisan --version
Laravel Framework version 5.2.43

oriceon/oauth-5-laravel のインストール

composer.json に下記を追加

    "require": {
	"oriceon/oauth-5-laravel": "dev-master"
    }

composer update を実行

$ composer update

consumer key の取得

下記リンク先で予め consumer key を取得します
Pocket: Developer API

サンプルコード

.env に下記を追加

# oauth
POCKET_CLIENT_ID=Your-Consumer-Key

config/oauth-5-laravel.php

<?php

return [

	/*
	|--------------------------------------------------------------------------
	| oAuth Config
	|--------------------------------------------------------------------------
	*/

	/**
	 * Storage
	 */
	'storage' => '\\OAuth\\Common\\Storage\\Session',

	/**
	 * Consumers
	 */
	'consumers' => [

		'Pocket' => [
			'client_id'     => env('POCKET_CLIENT_ID'),
			'client_secret' => null,			// Pocket API doesn't have a secret key.
			'scope'         => [],
		],

	]

];

app/Http/Controllers/PocketController.php

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class PocketController extends Controller
{
	public function loginOAuth(Request $request)
	{
		// get pocket service
		$pocketService = \OAuth::consumer('Pocket',url('/loginresult'));

		// get request token
		$reqToken = $pocketService->requestRequestToken();
		$request->session()->put( 'code', $reqToken );

		// get pocketService authorization
		$url = $pocketService->getAuthorizationUri([ 'request_token' => $reqToken ]);

		// return to pocket login url
		return redirect((string)$url);
	}

	public function loginResult(Request $request)
	{
		// get data from request
		$code = $request->session()->get('code');

		// get pocket service
		$pocketService = \OAuth::consumer('Pocket',url('/loginresult'));

		// if code is provided get user data and sign in
		if ( ! is_null($code))
		{
			// This was a callback request from pocket, get the token
			$token = $pocketService->requestAccessToken($code);

			$params = [
				'consumer_key'	=> env('POCKET_CLIENT_ID'),
				'access_token'	=> $token->getAccessToken(),
				'sort' => 'newest',
				'count' => '1',
			];

			// get a post (test)
			$json_result = $pocketService->request('https://getpocket.com/v3/get', 'POST', json_encode($params), [ 'Content-Type' => 'application/json' ]);
			$result = json_decode( $json_result );

			dd($result);
		}
		else
		{
			dd('code is null.');
		}
	}
}

app/Http/routes.php

<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/

Route::get('login', 'PocketController@loginOAuth' );
Route::get('loginresult', 'PocketController@loginResult' );

autoload 更新

$ php artisan optimize

動作確認

テストサーバー起動

$ php artisan serve

WEBブラウザで http://localhost:8000/login を開いて下の画像のように表示されれば成功です
f:id:takaya030:20160830232658p:plain

Google Cloud Monitoring を使ってみた

Google Container Engine (GKE) のモニタリングについてのメモ

Cloud Monitoring の有効化

GKE でコンテンナクラスタを作成するときに "Cloud Monitoring を有効にする" にチェックを入れます。
初回は StackDriver のユーザー登録を行った後、使用可能になります。
2016年7月23日現在、BETA期間中につき無料で使用できます。
f:id:takaya030:20160723165359p:plain

モニター画面

クラスタ起動後、約30分経過するとグラフが表示され始めます。
GKE ではエージェントの設定は特に必要なく自動で行われるようです。
VMインスタンスだけではなく、GKE で起動している POD も監視対象になってます。オートスケールを行う環境ではありがたいですね。
f:id:takaya030:20160723170150p:plain

Kubernetes で Lumen を動かす

Google Container Engine (GKE) で Kubernetes を使って Lumen を動かしたときの手順メモ。
今回は ReplicationController で pod を 3 個立ててみました。

Docker イメージの作成

1 個の pod 内で nginx と php-fpm の 2 つのコンテナを起動させます。そのための Docker イメージを作成します。

nginx の Dockerfile

FROM nginx:latest
MAINTAINER takaya030

ADD server.conf /etc/nginx/conf.d/server.conf

server.conf

同じ pod のコンテナは同じホストで起動するので php-fpm を localhost:9000 で参照する設定にしています。

server {
    listen 80 default;
    server_name _;
    root /webapp/public;
    index index.php index.html index.htm;
    charset utf-8;

    access_log off;
    error_log off;

    rewrite ^(.+)/$ $1;

    location / {
        # try_files $uri $uri/ /index.php$is_args$args;
        try_files $uri /index.php?$query_string;
    }

    location ~ ^/index.php$ {
        fastcgi_pass localhost:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}

nginx イメージのビルド

$ docker build -t gcr.io/my_project_id/lumen_nginx .

php-fpm + lumen の Dockerfile

k8s_lumen フォルダはこちらで作成した Lumen の実行環境になります。

FROM php:7-fpm
MAINTAINER takaya030

RUN apt-get update \
  && apt-get install -y libfreetype6-dev libjpeg62-turbo-dev libpng12-dev libmcrypt-dev \
  && docker-php-ext-install pdo_mysql mysqli mbstring gd iconv mcrypt

RUN mkdir /webapp
COPY k8s_lumen /webapp

php-fpm イメージのビルド

$ docker build -t gcr.io/my_project_id/lumen_php7 .

イメージを Container Registry へ push

$ gcloud docker push gcr.io/my_project_id/lumen_nginx
$ gcloud docker push gcr.io/my_project_id/lumen_php7

ReplicationController の作成

rc-k8slumen.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: k8slumen
spec: 
  replicas: 3
  selector:
    app: weblumen
  template:
    metadata:
      name: k8slumen
      labels:
        app: weblumen
    spec:
      containers:
        - name: nginx
          image: gcr.io/my-project-id/lumen_nginx
          ports:
            - containerPort: 80
        - name: lumen
          image: gcr.io/my-project-id/lumen_php7
          ports:
            - containerPort: 9000

ReplicationController の作成

$ kubectl create -f rc-k8slumen.yaml
replicationcontroller "k8slumen" created

ReplicationController の作成確認

$ kubectl get rc
NAME       DESIRED   CURRENT   AGE
k8slumen   3         3         27s

service の作成

service-k8slumen.yaml

apiVersion: v1
kind: Service
metadata:
  name: lumentest
spec:
  ports:
    -
      port: 80
      targetPort: 80
  selector:
    app: weblumen
  type: LoadBalancer

service の作成

$ kubectl create -f service-k8slumen.yaml
service "lumentest" created

service の作成確認

$ kubectl get services
NAME         CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes   10.3.240.1     <none>        443/TCP   7m
lumentest    10.3.255.153                 80/TCP    17s

動作確認

この状態で pod が 3 個起動しています。

$ kubectl get pods -l app=weblumen -o wide
NAME             READY     STATUS    RESTARTS   AGE       NODE
k8slumen-5r9jc   2/2       Running   0          1m        gke-cluster-1-default-pool-a58af72c-3ais
k8slumen-itl2h   2/2       Running   0          1m        gke-cluster-1-default-pool-a58af72c-yx7i
k8slumen-xfed2   2/2       Running   0          1m        gke-cluster-1-default-pool-a58af72c-4rtd

service を作成してから 1 分程経過するとグローバルIP (EXTERNAL-IP) が割り当てられるので、 http://EXTERNAL-IP にブラウザでアクセスして以下のように表示されれば成功です。
f:id:takaya030:20160702234718p:plain
ロードバランスされているので、ブラウザをリロードした際、前回と別の pod にリクエストしたときは以下のように Server IP が変化します。
f:id:takaya030:20160702234754p:plain
f:id:takaya030:20160702234807p:plain

フェイルオーバー(failover)の検証

試しに pod を一つ削除してみます。

$ kubectl delete pod k8slumen-5r9jc
pod "k8slumen-5r9jc" deleted

kubernetes が pod の減少を検知して自動で pod を立ち上げます。
pod のリストを見ると新しい pod が追加されているのが分かります。

$ kubectl get pods -l app=weblumen -o wide
NAME             READY     STATUS    RESTARTS   AGE       NODE
k8slumen-itl2h   2/2       Running   0          31m       gke-cluster-1-default-pool-a58af72c-yx7i
k8slumen-w468c   2/2       Running   0          12s       gke-cluster-1-default-pool-a58af72c-3ais
k8slumen-xfed2   2/2       Running   0          31m       gke-cluster-1-default-pool-a58af72c-4rtd

後始末

service、ReplicationController の削除
ReplicationController を削除すれば pod も自動で削除されます

$ kubectl delete -f service-k8slumen.yaml
service "lumentest" deleted

$ kubectl delete -f rc-k8slumen.yaml
replicationcontroller "k8slumen" deleted