けけずんセルフハッキング

忘れそうなことをメモる。

2018年振り返り

はじめに

2018年の振り返りをしてみる。 毎年やるやる言ってやってなかったが、さすがに今年は色々と変化があったので記録として残しておきたいと思ったのがきっかけ。

月毎の振り返り、身につけたスキルなど書いていく。

月毎の振り返り

  • 1月
    • 色々考えて転職を決意する
      • 前職の OGS(沖電グローバルシステムズ) はとても良い環境だったが、前向きな理由で転職することにした
      • 元々Startupな企業に興味あったし、タイミング的にも丁度よいかなと思っていた
    • 前職の上司に転職を伝える
      • かなり驚いていた
      • 社員みんな良い人達ばかりだし、みんな頑張ってたから転職を伝えることに申し訳無さを感じた、すまんな...
      • ひとまず3月末に退職するということで話を進める
  • 2月
    • 転職先の 株式会社Re:Build で必要な技術を身につけるために勉強する
      • PHPとLaravelの勉強してた
      • PHPうんこって聞いてたけど、PHP7系は割と良いという感触があった
    • 正式に退職届を提出する
      • 会社のプリント用紙を使って退職届を作成した
      • 退職届を入れる封筒もプリント用紙で作成したのは内緒
    • 前職で引き継ぎ作業など考慮して4月末に退職することに
      • 転職先で4月からプロジェクトに入る予定だったが、ずらしてもらった
  • 3月
    • 前職で引き継ぎ作業を開始する
      • 担当してきた案件のドキュメント整理
      • 退職手続きも平行して実施
    • 親に転職する旨を伝える
      • 母親は勿体無い勿体無いと引き止めてくる
        • 人によると思うけど母親って大体こういうもんだよね、ブレーキ担当
      • 父親はやりたいようにやればいいと肯定してくれる
        • よく考えると、これまで自分がやりたいと言ったことに対して否定されたことがなかった気がする
      • どっちも思った通りの反応だったので、それはそれで良かった
  • 4月
    • 前職で引き継ぎ作業をしつつ有給休暇を消化する
      • 前職は4月に有給休暇(15日だっけ?)が付与されるので、全部消化しました
      • 週一勤務で引き継ぎ作業を実施、他は転職先のための勉強
    • 4月末に前職のOGSを退職する
      • 2年間で制御チームとインフラチームに所属
        • 1年目は制御チームに所属
          • 某電力会社で使われているシステムの構築を行った
          • Python、仮想環境でのシステム運用、ネットワーク監視などを学んだ
          • 直属の上司が凄く優秀な方だったため、社会人としてもエンジニアとしても非常に勉強になった
        • 2年目はインフラチームに所属
          • 某電力会社や外部企業のインフラ構築・保守を行った
          • 物理・論理ネットワークの構築や運用、セキュリティなどを学んだ
      • 2年間でいろいろ資格を取得
      • 本当に良い職場でした、2年間ありがとうございました
  • 5月
    • 株式会社Re:Build(以下、リビルドと表記する)に入社する
      • 自分と社長を合わせて合計3人のベンチャー企業
      • 人生初のリモートワーク、ワクワクすっぞ
    • リビルド初の開発案件に参加する
      • PHP, Laravelで開発を開始
      • インフラ構築なども担当する
        • ここで前職で学んだことが活きた、感謝感謝
    • ピアノ・ドラム教室に通い始める
      • 以前からピアノ弾きたいと思ってたので知り合いのピアノ教室に通い始める
      • ついでにドラムも教えてもらっている
  • 6月
    • リモートワークに不安を感じる
      • 入社したばかりというのもあり、最初のうちはリモートワークの自由さに何一つ不安を抱えることはなかった
      • 入社して一ヶ月してから、リモートワークを続けていくうちに謎の孤独感を感じ始める
        • 先に入社した同僚もそういった不安に駆られる時期があったらしい
    • 福岡で開催される「PHPカンファレンス福岡2018」にLT枠で登壇する
      • 人前でプレゼンすることに苦手意識があったが、やってみると割と面白かった
      • イベントの他のセッションも非常に勉強になった、テキストベースの勉強もいいけどこういったイベントで勉強することも大事だな
  • 7月
    • リモートワークに慣れる
      • 働き方に慣れてきて、不安感など感じなくなる
      • そういったものを感じる際は同僚に声をかけて一緒に作業をするなど、工夫するようになる
  • 8月
  • 9月
    • リビルドで別案件の開発を開始する
      • PHP, Laravelで開発を開始
      • これまでの期間で得たノウハウもあり、比較的スムーズに進む
      • そのときにフロント(HTML, CSS)のスキルが弱いなと感じた
    • Startup Weekend Okinawa Vol.8」に参加する
      • Startup Weekend Okinawaに二度目の参加、前回のこのイベントが転職のきっかけになった
      • 前回は人が提案したアイディアに便乗して参加していたが、今回は自分がアイディアを提案し、共感してくれた人たちとでチームを組んだ
        • いいメンバーに恵まれた、今でも交流を続けている
      • 3日間という短い期間だが、毎度多くの学びがあった
  • 10月
    • リモートワークを活かして旅行に行く
      • 愛知と岐阜に行ってきた
      • リモートワークだし試しに旅行しながら仕事するかと挑んだが、期限的に厳しい時期だったため平日は部屋に籠もって作業してた
      • コワーキングスペースとか周ってみたかったな、余裕あるときにまた行ってみたい
      • 愛知は味噌カツ、岐阜はタン麺が美味しかった
    • リビルドで自社サービスの開発を開始する
      • 10月頭に実施した開発合宿で考案されたサービスを自社サービスとして進めていくことが決定した
      • 社長が企業やエンジニアにインタビューしてアイディアをブラッシュアップしつつ、傍らで自分と他のメンバーで実装を進めていく
  • 11月
    • バンドを結成する
      • 何を言っているのか分からないだろうが、バンドを結成した
      • メンバーはリビルドの同僚と自分の親友の合計3人
      • たまに集まって練習している
    • Scrum Coaching Retreat Okinawa」に参加する
      • イベントの趣旨を分からず誘われるがままローカルスタッフとして参加した
      • 普段関われないような方々と交流できた
      • レトリートよい、〇〇レトリートとして他にもやってみたい
    • リビルドの自社サービスが「Okinawa STARTUP PROGRAM」に採択される
      • 社長めっちゃ頑張ってた、ありがとうございます
      • 今後も頑張る
  • 12月
    • リビルドで担当している案件でGolangを導入する
      • 以前から勉強はしていたものの、実務で使用したことがなかったGolangを使用して開発を開始した
      • 静的言語楽しい、コンパイルでお叱りを受けてそれを直していく感じ楽しい
    • 宮崎で開催される「Webナイト宮崎 Vol.2」にLT枠で登壇する
      • 自社サービス開発メンバーのお誘いで急遽参加させてもらった
      • 宮崎また行きたい

身につけたスキル

プログラム言語

フレームワーク

  • Laravel5
  • Vue.js
  • Nuxt.js

その他

  • Docker
  • AWSいろいろ

おわりに

2018年は転職をすることで仕事環境が一変するだけでなく、プライベートも充実した年だった。 リビルド初期メンバーとの出会いがなければ、今の自分はいないのだろうなと深く感じている。 素晴らしい環境と、その場所を作ってくれた社長に感謝。

2019年も頑張るぞい。

Nuxt.jsでページ遷移後に動作するくるくる(ローディングバー)をmixin使って実装する

はじめに

Nuxt.jsでページ遷移後のローディング時に動くくるくるをデフォルトではなく、独自のくるくるを使いたい。 また毎回各コンポーネントに独自くるくる起動・終了の定義をするのが面倒くさいのでmixinを使って実装を行う。

実装手順

以下にやることを列挙する。

ここまでやれば独自くるくるが動くはず。

独自ローディング用コンポーネントを用意する

サンプルとして、ここのloading.vueを使用する。 components/loading.vueを以下のように編集する。

<template>
  <div v-if="loading" class="loading-page">
    <p>Loading...</p>
  </div>
</template>

<script>
export default {
  data: () => ({
    loading: false
  }),
  methods: {
    start() {
      this.loading = true
    },
    finish() {
      this.loading = false
    }
  }
}
</script>

<style scoped>
.loading-page {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.8);
  text-align: center;
  padding-top: 200px;
  font-size: 30px;
  font-family: sans-serif;
}
</style>

start()finish()は定義必須。

nuxt.conf.jsのloadingに上記コンポーネントを指定する

nuxtで動くデフォルトのくるくるに、先程作成したloading.vueを指定する。 nuxt.conf.jsを以下のように編集する。

export default {
  loading: '~/components/loading.vue'
}

mixin用コンポーネントを用意する

以下のようにcomponents/loadingMixin.jsを作成する。 拡張子を間違えないように注意。

export default {  
  data() {  
    return {  
      loadingTime: 1000  
    }  
  },  
  async mounted() {  
    this.$nextTick(() => {  
      this.$nuxt.$loading.start();  
      setTimeout(() => this.$nuxt.$loading.finish(), this.loadingTime);  
    })  
  }  
}

これによりmixinを使用するコンポーネントがマウントされたタイミングで独自くるくるが描画される。 なおthis.$nuxt.$loading.start()はnuxt.conf.jsで指定した独自くるくるの描画を開始を、this.$nuxt.$loading.finish()は描画の終了を行っている。

ローディングを呼び出したい各コンポーネントでmixinを指定する

以下のようにpages/index.vue, pages/about.vueを作成する。

pages/index.vue

<template>
  <div>
    <p>これはindexです。</p>
    <nuxt-link  to="/about">
      aboutに遷移します。
    </nuxt-link>
  </div>
</template>

<script>
import loadingMixin from '@/components/loadingMixin';

export default {
  mixins: [loadingMixin],
}
</script>

pages/about.vue

<template>
  <div>
    <p>これはaboutページです。</p>
    <nuxt-link  to="/">
      indexに遷移します。
    </nuxt-link>
  </div>
</template>

<script>
import loadingMixin from '@/components/loadingMixin';

export default {
  mixins: [loadingMixin],
}
</script>

動作確認

以下のコマンドを実行し、 http://localhost:3000 にアクセスする。 indexとaboutページをそれぞれ遷移すると、遷移後に独自くるくるが動くことが確認できるはず。

$ npm run dev

おわりに

あとはcomponents/loading.vueを好きなように書き換えるだけで色んなくるくるを再現できる。良いくるくるライフを。

参考

「NuxtMeetUp#5」に参加しました

※会社の技術ブログが未だに復旧していないので、一時的にこちらに掲載します

株式会社リビルドの嘉数です!

10/18(木)に渋谷で開催されたNuxtMeeutup#5に参加してきました、渋谷です。基本的に嘉数は渋谷にいるときは楽天カフェで作業してます。楽天ペイで支払うとカフェ半額だし、電源もあるし素晴らしい。

NuxtMeetUp#5(再度枠増加) - connpass

はい、NuxtMeetup#5について書いていきたいと思います。

NuxtMeetup#5に参加した理由

かんぼ(社長)「自社サービスはSPAでいこう(突然)」

最近から開発を始めた自社サービス、社長の意向でSPA化することになりました。嘉数は普段はバックエンドの開発をしている人間ですが、フロントエンドにも興味があったため良い機会と思いSPA化に賛成しました。

かんぼ(社長)「SPA化する際はNuxt.jsを使おう」

嘉数「Vue.js+VueRouterでよくないですか?」

かんぼ(社長)「とりあえず流行にのっておけ」

なるほど。嘉数は流行に疎いのですが、流行にのるのは大事だなと思いNuxt.jsを導入することにしました。あと触ったことが無いものを触るのって凄く楽しいですよね。

そんなわけでNuxt.jsを導入する前にある程度どういったものか情報収集することと、Nuxt.jsを使って普段から開発を行っている人たちと関わることを目的にNuxtMeetup#5に参加しました。

NuxtMeetup#5について

connpassのNuxtMeetup#5募集ページを見ると、参加枠の人数が160人に達していることが分かります。非常に多い。嘉数もインフラ系のイベントにはよく参加するのですが、そういったイベンドでは多くてもせいぜい30人程度でした。フロントエンド界隈の、今回で言えばNuxt.jsの人気具合が伝わりますね。半信半疑だったけどちゃんと流行ってたんだな...。

LTセッションでは、はじめの挨拶とスポンサーLTを含めて合計8人の方が登壇していました。Nuxtがビルドされた際に出力されるバンドルサイズでかすぎる問題、nodeサーバをクラスタモードにしてマルチスレッドで捌く話、Nuxtのハマりどころ、nuxt-i18nを使って多言語対応したけどSEO対策するとheadタグの中身が消える話、NuxtでJAMStackな開発をする話など、様々な話題がLTセッションで繰り広げられました。以下は本イベント内で使用された、現在アップロードされている分のスライドになります。

おわりに

今回はNuxtMeetup#5に参加するためだけに当日早朝から東京入りし、翌日の朝に沖縄に帰りました。沖縄・東京間は流石に結構な旅費がかかりましたが、本イベントに参加したことはそれ以上の価値があったなと思います。情報収集も出来たし、何よりNuxt友達が出来たことが嬉しかったです。イベントを運営してくれたスタッフの皆様、有意義な時間を本当にありがとうございました!

広告・告知など

弊社(株式会社リビルド)は沖縄県内でLaravel勉強会、JS勉強会など様々なイベントを開催しています。沖縄県内にいる方はもちろん、本土の人も沖縄旅行も兼ねてイベントに参加していただけると嬉しいです。

LaradockをHTTPS化

ファイル編集

laradockの以下のファイルを編集する。

nginx/Dockerfile

FROM nginx:alpine

LABEL maintainer="Mahmoud Zalt <mahmoud@zalt.me>"

ADD nginx.conf /etc/nginx/

# If you're in China, or you need to change sources, will be set CHANGE_SOURCE to true in .env.

ARG CHANGE_SOURCE=false
RUN if [ ${CHANGE_SOURCE} = true ]; then \
    # Change application source from dl-cdn.alpinelinux.org to aliyun source
    sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/' /etc/apk/repositories \
;fi

RUN apk update \
    && apk upgrade \
    && apk add --no-cache bash \
    && adduser -D -H -u 1000 -s /bin/bash www-data

RUN apk add --no-cache openssl \
    && mkdir /etc/nginx/ssl 2> /dev/null \
    && openssl genrsa -out "/etc/nginx/ssl/localhost.key" 2048 \
    && openssl req -new -key "/etc/nginx/ssl/localhost.key" -out "/etc/nginx/ssl/localhost.csr" -subj "/CN=localhost/O=localhost/C=UK" \
    && openssl x509 -req -days 365 -in "/etc/nginx/ssl/localhost.csr" -signkey "/etc/nginx/ssl/localhost.key" -out "/etc/nginx/ssl/localhost.crt"

ARG PHP_UPSTREAM_CONTAINER=php-fpm
ARG PHP_UPSTREAM_PORT=9000

# Set upstream conf and remove the default conf
RUN echo "upstream php-upstream { server ${PHP_UPSTREAM_CONTAINER}:${PHP_UPSTREAM_PORT}; }" > /etc/nginx/conf.d/upstream.conf \
    && rm /etc/nginx/conf.d/default.conf

CMD ["nginx"]

EXPOSE 80 443

nginx/sites/default.conf

server {

    listen 80 default_server;
    listen 443 ssl default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name localhost;
    root /var/www/public;
    index index.php index.html index.htm;

    ssl_certificate /etc/nginx/ssl/localhost.crt;
    ssl_certificate_key /etc/nginx/ssl/localhost.key;

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

    location ~ \.php$ {
        try_files $uri /index.php =404;
        fastcgi_pass php-upstream;
        fastcgi_index index.php;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        #fixes timeouts
        fastcgi_read_timeout 600;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt/;
        log_not_found off;
    }
}

ビルド

以下のコマンドでビルド、起動する。

$ docker-compose build nginx
$ docker-compose up -d nginx

Laravel5.5で任意のエラー画面を出すときに注意した方がいいこと

はじめに

Laravel5.5で任意のエラー画面を出したくて奮闘した話。最初は下記の要件を満たしたいだけだった。

  • abort関数でエラーコードに応じた任意の画面を出力する
  • ぬるぽや未定義変数を参照するなど想定外のエラーの際に任意の画面を出力する

これをネットの記事を参考にし、Laravelで例外処理をハンドリングしてるであろうapp/Exceptions/Handler.phprenderメソッドを以下のように編集した。

public function render($request, Exception $exception)
{
+    if ($this->isHttpException($exception)) {
+        if ($exception->getStatusCode() == 400) {
+            return response()->view('errors.400', [], 400);
+        }
+    } else {
+        return response()->view('errors.500', [], 500);
+    }
+
-    return parent::render($request, $exception);
}

とりあえずこれでabort関数で任意のページを表示してくれるし、想定外のエラーが起きた際にも500システムエラーページを表示してくれるようになった。やったぜ!と喜んでいるのもつかの間、問題が発生する。

問題発生!

入力されたデータに対する自動バリデーションが思うように動作しなくなった。なんでだろうと思い編集前のapp/Exceptions/Handler.phprenderメソッドを見直すと、

return parent::render($request, $exception);

こいつが目に入った。中を覗いてみると以下のようになっていた。

public function render($request, Exception $e)
{
    if (method_exists($e, 'render') && $response = $e->render($request)) {
        return Router::toResponse($request, $response);
    } elseif ($e instanceof Responsable) {
        return $e->toResponse($request);
    }

    $e = $this->prepareException($e);

    if ($e instanceof HttpResponseException) {
        return $e->getResponse();
    } elseif ($e instanceof AuthenticationException) {
        return $this->unauthenticated($request, $e);
    } elseif ($e instanceof ValidationException) {
        return $this->convertValidationExceptionToResponse($e, $request);
    }

    return $request->expectsJson()
                    ? $this->prepareJsonResponse($request, $e)
                     : $this->prepareResponse($request, $e);
}

ValidationExceptionの文字が見える。なるほど、parent::renderメソッドでバリデーションとっていたのか。今回はparent::renderメソッドを消していた、道理でバリデートされないわけだ。

無事解決!

app/Exceptions/Handler.phprenderメソッドを再び修正する。

public function render($request, Exception $exception)
{
+    if ($this->isHttpException($exception)) {
+        if ($exception->getStatusCode() == 400) {
+            return response()->view('errors.400', [], 400);
+        }
+    }
+
+    if ($exception instanceof ErrorException) {
+        return response()->view('errors.500', [], 500);
+    }
+
    return parent::render($request, $exception);
}

上記のコードの通り$exceptionHttpExceptionのときは任意のエラーページを表示し、想定していないエラーについてはErrorExceptoinで補足してシステムエラーページを表示する。さらにHttpExceptionErrorException以外の何かしらの例外やエラーについてはparent::renderメソッドで処理をするようにし、自動バリデーションなど適切に処理が行われるようにした。

おわりに

既存のコードに変更を加える前にしっかりと周辺のコードを読み込まないとアカンですね。気をつけよう。

参考

「レガシーコード改革!UT/CIでWebサービスの技術的負債を解消する取り組み」に登壇しました

※会社のブログが移行中で記事作成できないので、一時的にこちらに書いてます

株式会社リビルドの嘉数です!

8/7(火)にランサーズ、BASE、リビルドの三社で「レガシーコード改革!UT/CIでWebサービスの技術的負債を解消する取り組み」というイベントを実施し、リビルド枠として嘉数は「Laravel5.5的継続的インテグレーション」というタイトルで登壇させて頂きました。

【好評につき増枠!】レガシーコード改革!UT/CIでWebサービスの技術的負債を解消する取り組み - connpass

というわけで各セッションについて非常にざっくりとした感じで書いていきたいと思います!

「決済リプレイスとPHPバージョンアップを支えたユニットテスト

スピーカーはBASE株式会社の東口和暉さん。ユニットテストについてお話して頂きました。

  • テストをしやすくするためにコードを疎結合にしてMockを差し込めるようにする、テストしにくい部分は破壊的だがrunkitを使用してコード内部の処理を変更する
  • テストを書く人・書かない人に分かれないよう全員がテストを書きやすい環境を整える
  • 溜まってきたテストの知見を活用して「破綻しやすいテスト」を書いていないか確認する

手探りで何が最適なのかを探しながらテストコードを書いている自分としては、非常に共感が得られる内容でした!

「レジェンドコードと向き合いレガシーからモダンへ変革する」

スピーカーはランサーズ株式会社の永田真也さん。ランサーズがこれまで行ってきた開発の歴史、もっと言えばテストの歴史についてお話して頂きました。

  • アプリ自体はPHPで実装しているが、E2EテストはRSpec+Capybaraのように疎結合に導入できるものを使用した
  • 技術的負債を解決するために多くの失敗を重ねるが、その失敗と向き合い改善を続けて前に進み続ける、徐々にコードを進化させていくことを意識する

起業から11年経った会社のテストに対する取り組みの変化は非常に面白かったです!

弊社の発表!「Laravel5.5的 継続的インテグレーション

発表!

そしてこちら弊社のプレゼン資料、タイトルで「〜的〜的」と的が連続しているのが気になります。

こちら発表風景になります。

20180807_レガシーコード改革01.jpg - Google ドライブ

20180807_レガシーコード改革02.jpg - Google ドライブ

質疑応答!

弊社の発表後にいくつか質問を頂いたのですが、質問内容が興味深いものでした。以下はその時の質問と回答の内容になります。

  • Q:「LaravelのPHPUnitでテストを書く際、どこからどこまでがユニットテスト結合テストの範囲なのか?」
  • A:「現在の開発であれば、結合テストには"機能の一連の手続き"と"コントローラ内のアクション"についてのテストを記述している。ユニットテストには"他に依存しない独立されたサービスクラス"のテストを記述している。ここらへんの配分って難しい、分かりみ。」

Webアプリだとユニットテスト結合テストの境目がどこからやねーんってなりますよね。皆も同じように思ってるんだなぁと感動しました(笑)。

  • Q:「GitLab CIでコンテナを使用してテストをしているようだが、最終的なデプロイ先もコンテナなのか?そうでない場合は冪等性が保証できないが、環境のテストも行っているのか?」
  • A:「今回はデプロイ先はEC2を想定していたので、コンテナは使用していない。また、環境のテストは行っていない。」

この「環境のテスト」という考えがとても面白いなと思いました。テストをする環境とデプロイ先の環境が全く同じであれば環境のテストは必要ないかもしれませんが、これらの環境が異なる場合は何が異なっていてどういった影響があるかなど確認しておく方が良さそうですね。かなり勉強になりました。

おわりに

今回のイベントでは、テストについて自分だけでなく皆が四苦八苦しながら取り組んでいるのだなと感じました。共感を得られて且つ知見の共有にもなる、とても良いイベントでした。イベントを主催してくれた方々、スタッフの方々、参加してくれた方々、皆さん本当にありがとうございました。そして懇親会のお肉が最高だったことは忘れません、ありがとう。

Gitlab CI/CDでLaravelプロジェクトのテスト及びデプロイをする

概要

LaravelプロジェクトをGitlabにPushした際、Pushしたブランチに応じて自動でテストやデプロイを行うようにする。ついでに、テストが通らなかったブランチはメインのブランチに対するマージリクエストを行えないようにする。

ブランチ毎に行う処理

  • master:テスト、本番環境へのデプロイ
  • deploy:テスト、ステージング環境へのデプロイ
  • 上記以外のブランチ:テスト

処理の流れ

以下はPushからデプロイまでの処理の流れ図。

20180322_GitlabCI_laravel.png - Google ドライブ

  1. LocalからGitlabにPushする
  2. GitlabCIが実行される
  3. Unitテスト用のDockerコンテナが起動される
  4. Unitテストが実行される
  5. デプロイ用のDockerコンテナが起動される
  6. Envoyコマンドが実行される
  7. デプロイ先(本番環境orステージング環境)でPullされる

実装手順

結構やること多い。やることを列挙しとく。

  • ローカルでやること
    • Gitlabからデプロイ先に接続するための鍵組の作成
    • Dockerの準備
      • Dockerfileの作成
      • Dockerfileをビルドしてイメージを作成
      • DockerイメージをGitlab Container Registryに登録
    • Gitlab-CI設定ファイルの作成
    • デプロイ用タスクランナー設定ファイルの作成
  • デプロイ先(本番環境orステージング環境)でやること
    • デプロイ用ユーザの作成と設定
    • GitlabからClone・Pullするための鍵組の作成
    • リポジトリのCloneと設定
    • Gitlabからデプロイ先に接続するための鍵の設置
  • Gitlabでやること
    • GitlabからClone・Pullするための鍵を登録
    • Gitlabからデプロイ先に接続するための鍵の登録
    • ブランチをProtected branch化

こんな感じでやっていく。

ローカルでやること

Gitlabからデプロイ先に接続するための鍵組の作成

ローカルの任意の場所(以下/path/to/mykeysとする)で以下のコマンドを実行し、Gitlabからデプロイ先へ接続を行うための鍵組を作成する。

$ cd /path/to/mykeys
$ ssh-keygen -t ecdsa -b 521 -f gitlab-deploy-key -C "deploy-key@gitlab.com"
(色々聞かれるので全てEnter)

暗号スイートはECDSAを指定、鍵長は521bitを指定、コメントは任意のコメントを入力する。以下のコマンドで鍵組(秘密鍵、公開鍵)が作成されたことを確認する。

$ ls -l /path/to/mykeys
gitlab-deploy-key
gitlab-deploy-key.pub

これ失くしたらヤバイので、紛失しないようにちゃんと管理すること。

Dockerの準備

Gitlab-CI実行時に起動されるDocekrの準備をする。Dockerイメージの登録先として、Gitlabにmydockerというリポジトリを作成しておくこと。

Dockerfileの作成

ローカルの任意の場所(以下/path/to/mydockerとする)で以下のコマンドを実行し、Dockerfileファイルを作成する。

$ touch /path/to/mydocker/Dockerfile

上記で作成したファイルに以下の内容を記述する。

FROM centos:7
MAINTAINER myname <myname@my_email_address>

RUN echo "include_only=.jp" >>/etc/yum/pluginconf.d/fastestmirror.conf && \
    rpm --import http://ftp.riken.jp/Linux/centos/RPM-GPG-KEY-CentOS-7 && \
    rpm --import http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7 && \
    rpm --import http://rpms.famillecollet.com/RPM-GPG-KEY-remi

RUN yum -y install epel-release && \
    yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm && \
    yum-config-manager --enable remi-php72 && \
    yum -y update

RUN yum -y install wget git unzip

RUN yum -y install php php-mbstring php-pdo php-mysqlnd php-dom php-posix

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

RUN composer global require "laravel/envoy=~1.0"

今回のDockerfileはLaravelプロジェクトとEnvoyコマンドが動く最低限の環境を構築するための内容になっている。他に必要なものがあれば上記のファイルに適宜追加してね。

Dockerfileをビルドしてイメージを作成

作成したDockerfileから以下のコマンドでDockerイメージを作成する。

$ cd /path/to/mydocker
$ docker build -t registry.gitlab.com/[Gitlabアカウント名]/mydocker .

-tオプションには作成するイメージ名を指定してる。ビルドに少し時間かかるかも(3〜5分くらい)。

DockerイメージをGitlab Container Registryに登録

GitlabにDockerイメージを登録するために以下のコマンドでGitlabにログインする。

$ docker login registry.gitlab.com
(Gitlabアカウント名、パスワードの入力を要求される)

ログイン後、以下のコマンドでDockerイメージを登録する。

$ docker push registry.gitlab.com/[Gitlabアカウント名]/mydocker

ここでも少し時間がかかる(3〜5分くらい)。特にエラーがなければ正常に登録されているはず。

なお、Gitlab公式ページではLaravelプロジェクト直下にDockerfileを作成してビルドしたDockerイメージをLaravelプロジェクトのGitlabリモートリポジトリに登録しているが、今回はDocker用にGitlabリモートリポジトリに作成してそこに登録している。こうすることで、他のプロジェクトでもDockerを利用することが出来る(こういう使い方していいのか分からんけど)。

Gitlab-CI設定ファイルの作成

Gitlab-CIではGitlab-CI設定ファイル.gitlab-ci.ymlに記述された内容に沿ってCIが実行される。以下のコマンドでLaravelプロジェクト直下(以下/path/to/myappとする)に.gitlab-ci.ymlファイルを作成する。

$ touch /path/to/myapp/.gitlab-ci.yml

上記で作成したファイルに以下の内容を記述する。

image: registry.gitlab.com/[Gitlabアカウント名]/mydocker

stages:
  - test
  - deploy

test_pupunit:
  stage: test
  services:
    - mysql:latest
  variables:
    DB_HOST: mysql
    DB_USERNAME: root
    MYSQL_DATABASE: homestead
    MYSQL_ROOT_PASSWORD: secret
  script:
    - cp .env.example .env
    - composer install
    - php artisan key:generate
    - php artisan migrate
    - vendor/bin/phpunit

.shared_hidden_key: &before_deploy
  before_script:
    - 'type ssh-agent || yum -y install openssh-clients'
    - eval $(ssh-agent -s)
    - ssh-add <(echo "$SSH_PRIVATE_KEY")
    - mkdir -p ~/.ssh
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >~/.ssh/config'

deploy_stg:
  stage: deploy
  <<: *before_deploy
  script:
    - ~/.composer/vendor/bin/envoy run deploy_stg
  environment:
    name: staging
    url: [ステージング環境で公開してるURL]
  when: on_success
  only:
    - develop

deploy_prd:
  stage: deploy
  <<: *before_deploy
  script:
    - ~/.composer/vendor/bin/envoy run deploy_prd
  environment:
    name: production
    url: [本番環境で公開してるURL]
  when: manual
  only:
    - master

image項目では先ほどGitlabに登録したDockerイメージを参照してあげる。あとはテストの項目、デプロイの項目が順に記述されている感じ。あとデプロイ先毎に同じ内容の処理を記述するのがダサい感じがしたので、.shared_hidden_key: &before_deployの項目に処理をまとめて記述しておいて、各デプロイ先の処理部(deploy_stg, deploy_prd)に <<: *before_deployを記述しておく。これでなんとなくスマートな感じがする。

.gitlab-ci.ymlがローカルリポジトリに存在する状態でGitlabにPushするとGitlab-CIが実行されるようになる。しかし今の段階ではちゃんと動かないので、本ブログの手順を全て終えてから最後にPushしてね。

デプロイ用タスクランナー設定ファイルの作成

Gitlab-CIから呼び出されるデプロイ用タスクランナー設定ファイルEnvoy.blade.php/path/to/myapp直下に作成する。

$ touch /path/to/myapp/Envoy.blade.php

上記で作成したファイルに以下の内容を記述する。

@servers(['stg-host' => 'deployer@[ステージング環境のIPアドレス]', 'prd-host' => 'deployer@[本番環境のIPアドレス]'])

@setup
  $app_dir = '/var/www/myapp'
@endsetup

@story('deploy_stg', ['on' => 'stg-host'])
  pull_repository
  run_composer
  run_artisan
  run_npm
@endstory

@story('deploy_prd', ['on' => 'prd-host'])
  pull_repository
  run_composer
  run_artisan
  run_npm
@endstory

@task('pull_repository')
  echo 'Pull repository'
  cd {{ $app_dir }}
  git pull
@endtask

@task('run_composer')
  cd {{ $app_dir }}
  composer update
  composer install --no-dev --optimize-autoloader
@endtask

@task('run_artisan')
  cd {{ $app_dir }}
  php artisan down
  php artisan migrate
  php artisan cache:clear
  php artisan config:cache
  php artisan view:clear
  php artisan up
@endtask

@task('run_npm')
  cd {{ $app_dir }}
  npm run production
@endtask

デプロイ先(本番環境orステージング環境)でやること

デプロイ先(本番環境、ステージング環境)それぞれにログインして以下の作業を行う。

デプロイ用ユーザの作成と設定

rootユーザで以下のコマンドを実行し、デプロイ用のユーザを作成する。

root$ adduser deployer

どうせ公開認証方式でしかログインしないだろうからパスワードの設定しなくてもいいんだけど、念のため設定しておく。

root$ passwd deployer
(任意のパスワードを入力)

deployerユーザが/var/www下でリポジトリをClone・Pull出来るよう、以下のコマンドで権限を与える。/var/wwwがない場合は作成すること。

root$ setfacl -R -m u:deployer:rwx /var/www
GitlabからClone・Pullするための鍵組の作成

deployerユーザで以下のコマンドを実行し、GitlabからClone・Pullを行うための鍵組を作成する。

deployer$ mkdir -p ~/.ssh
deployer$ chmod 700 ~/.ssh
deployer$ ssh-keygen -t ecdsa -b 521
(色々聞かれるので全てEnter)

以下のコマンドで鍵組(それぞれ秘密鍵、公開鍵)が作成されたことを確認する。

deployer$ ls -l ~/.ssh
id_ecdsa
id_ecdsa.pub
リポジトリのCloneと設定

deployerユーザで以下のコマンドを実行し、GitlabからLaravelプロジェクトをCloneする。

deployer$ cd /var/www
deployer$ git clone git@gitlab.com:[Gitlabアカウント名]/myapp.git

CloneしてきたLaravelプロジェクト下にあるstorageにのみ書き込み権限を与える。

deployer$ chmod -R 777 /var/www/myapp/storage

chmodコマンドで-Rオプションつけて777って場所間違えたら大変なことになるので注意。

Gitlabからデプロイ先に接続するための鍵の設置

ここが少しややこしい。手順「ローカルでやること/Gitlabからデプロイ先に接続するための鍵の作成」で作成した鍵のうち公開鍵gitlab-deploy-key.pubをデプロイ先に設置する。以下のコマンドをローカルで実行し、公開鍵を転送する。

$ scp /path/to/mykeys/gitlab-deploy-key.pub deployer@[デプロイ先のIPアドレス]:~/

公開鍵認証使ってる人は上記のコマンドを実行すると特に何も聞かれずに実行完了するけど、使ってない人はパスワード求められるのでパスワードを入力する。さっき設定したパスワードがここで活きるというね、設定しててよかった(小並感)。

次に以下のコマンドをデプロイ先で実行し、転送されてきた公開鍵をauthorized_keysに記述する。あとは適切なアクセス権を付与することで、Gitlabからデプロイ先へ接続可能な状態になる。最後に、転送されてきた公開鍵を削除するのを忘れずに。

deployer$ cat ~/gitlab-deploy-key.pub >> ~/.ssh/authorized_keys
deployer$ chmod 600 ~/.ssh/authorized_keys
deployer$ rm ~/gitlab-deploy-key.pub

コマンドの実行場所(ローカルとデプロイ先)を間違えないように注意しよう。

Gitlabでやること

ここからはブラウザでGitlabのページを開いて作業を行う。ローカルにあるLaravelプロジェクト/path/to/myappのリモートリポジトリはGitlabに作成済みであるとする。

GitlabからClone・Pullするための鍵を登録

ここもややこしい。手順「デプロイ先でやること/GitlabからClone・Pullするための鍵の作成」で作成した鍵のうち公開鍵id_ecdsa.pubをGitlabに登録する。以下のコマンドをデプロイ先で実行し、出力された値をコピーする。

deployer$ cat ~/.ssh/id_ecdsa.pub
(なんか出力されるので範囲選択してコピーする)

LaravelプロジェクトのGitlabリモートリポジトリページからProject > Settings > Repositoryと辿っていき、Deploy Keysの項目で登録する鍵のタイトルを任意で入力、それと先ほどコピーした公開鍵の内容をペーストする。以下のような感じ。入力後はAdd keyボタンを押して登録する。

20180322_Gitlab_deploy-key_registration.png - Google ドライブ

Gitlabからデプロイ先に接続するための鍵の登録

またもやここもややこしい。手順「ローカルでやること/Gitlabからデプロイ先に接続するための鍵の作成」で作成した鍵のうち秘密鍵gitlab-deploy-keyをGitlabに登録する。以下のコマンドをローカルで実行し、出力された値をコピーする。

$ cat /path/to/mykeys/gitlab-deploy-key
(なんか出力されるので範囲選択してコピーする)

LaravelプロジェクトのGitlabリモートリポジトリページからProject > Settings > CI/CDと辿っていき、Secret variablesの項目で登録する鍵のタイトルに「SSH_PRIVATE_KEY」と入力し(重要!!)、先ほどコピーした秘密鍵の内容をペーストする。「Protected」のトグルはよく分からんけどオンにしておく。以下のような感じ。入力後はSave variablesボタンを押して登録する。

20180322_Gitlab_secret-key_registration.png - Google ドライブ

鍵のタイトルに「SSH_PRIVATE_KEY」を入力したのは、手順「ローカルでやること/Gitlab-CI設定ファイルの作成」で作成したGitlab-CI設定ファイル内で環境変数として使用するため。鍵のタイトルとGitlab-CI設定ファイルに記述した環境変数名は統一すれば何でも良いのだけど、分かりやすいように今回はこのタイトルにした。

特定のブランチをProtected branch化

うおーーーーーそしてこれが最後の手順!Gitlab-CIでテストを実行した後、テストが通ったブランチをメインのブランチにマージしないよう設定する(正確にはマージリクエストが出来ないようにする、かな?)。

LaravelプロジェクトのGitlabリモートリポジトリページから、Project > Settings > Repositoryと辿っていき、Protected Branchesの項目でProtected branch化したいブランチを選択する。おそらく本番環境で使用するmasterブランチはデフォルトでProtected branchになってるので、今回はステージング環境で使用するdevelopブランチ辺りをProtected branch化する。以下のような感じ。

20180322_Gitlab_Protected-branch.png - Google ドライブ

Gitlab-CI実行

ここまで長い工程をお疲れ様でした。最後に、ローカルからLaravelプロジェクト/path/to/myappをPushし、Gitlab-CIが実行されるか確認しよう。このとき、本番環境にデプロイするならmasterブランチ、ステージング環境にデプロイするならdevelopブランチでPushする。最初に説明したが、developブランチはPushすると自動でデプロイされるが、masterブランチはPushしても自動でデプロイはされない。masterブランチをデプロイする際はLaravelプロジェクトのGitlabリモートリポジトリページからテストが通っていることを確認し、手動でデプロイすること。

$ cd /path/to/myapp
$ git push -u origin master

感想

主にGitlab公式ページを参考に作業を行ったが、実際に手を動かして理解できた。作業自体も時間がかかったが、この記事を書くのにも時間かかった...。自分のための記録として、また他の人の参考として、本記事が役に立つと嬉しい。間違い等があれば指摘おなしゃす、

参考