GitHub Actions で Laravel + Vue 環境を自動デプロイ

[GitHub Actions](https://github.com/features/actions) が正式リリースされましたね。
これで外部サービスを使用しなくても GitHub 単体で CI/CD などのワークフローを自動化できるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6102/dd68d908-7127-312f-779e-1a46e32e3f63.png)
https://github.com/features/actions

弊社は社内の GitLab でソースコード管理+ CI/CD していますが、ちょうど担当していた案件の納品先が GitHub ということもあり、Laravel + Vue アプリケーションをビルドし本番サーバにデプロイするところまでを実装してみました。

## ゴール
特定のブランチに push すると自動で本番環境にデプロイする

## 流れ
ワークフローとして実行するタスクはこんな感じです。

- 特定のブランチ(master)へのプッシュ(or プルリク) をトリガーにワークフローを起動
- GitHub 上で docker コンテナを起動
    - 以下コンテナ上で
    - git clone (ソースコード取得)
    - composer install (PHP環境構築)
    - npm install (node環境構築)
    - npm run prod (フロントエンドビルド)
- rsync でコンテナ上で構築したファイルを本番サーバへ転送


## 秘匿情報をいかにセキュアに管理するか
今回の案件ではフロントエンド(Vue.js)=>バックエンド(Laravel)間の API 通信の認証に Laravel Passport を使用していました。Laravel Passport では認証用のトークンをフロントエンドのコードに含めてビルドする必要があるため、トークンをコンテナ内に持ち込む必要がありますが、他にも DB のパスワードや、AWS のトークンなど、またビルドした成果物を rsync + ssh で本番サーバに同期する際の秘密鍵、などなど、知られてはいけない情報が多々あり、当然これら **セキュアな情報を記載したコードをリポジトリにコミット出来ない問題** に直面します。

## 秘匿情報は Secrets に保存
これを解決するために、GitHub のリポジトリ設定画面には Secrets というメニューが追加されており、セキュアな情報に名前を付けて保存できます。これは GitHub Actions だけに使用する目的で追加された機能で、

> Secrets are environment variables that are encrypted and only exposed to selected actions. Secrets are not passed to workflows that are triggered by a pull request from a fork of this repository. 

とある通り、情報は暗号化され、選択されたアクションにのみ使用されれます。リポジトリのフォークで継承されることもなく、また、他のコントリビューターからも閲覧されることはありません。（そもそも登録した本人でさえ一度登録した内容は閲覧できません）

Secrets は、GitHub のリポジトリメニューの「Settings」からサイドメニューの「Secrets」より遷移し、「Add a new secret」をクリックして、Name と Value を登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6102/62d8b359-9111-8459-81e3-58f9a657f281.png)

今回はここに、Laravel Passport の認証用トークン `OAUTH_CLIENT_SECRET` と rsync で使用する SSH の秘密鍵 `SSH_PRIVATE_KEY` を登録しました。これらの Name はワークフローの定義時に変数として使用することができるので後述の定義ファイル自体はセキュアな記述となります。

```yaml
        MIX_OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }}
```

### ワークフローの作成
それではワークフローを作っていきましょう。リポジトリのタブメニューにある Actions をクリックすると予め用意されたワークフローが表示されます。これから作成するワークフローに近いものがあればそれをテンプレートとして使用しても良いですが、今回は使用しません。実は画面から作成しなくても、定義ファイルを直接リポジトリに追加しプッシュすればワークフローとして認識します。

定義ファイルは下記のパスになります。

.github/workflows/main.yml

拡張子からも分かる通り、GitHub Actions ではワークフローを YAML 形式で定義します。

```yaml
name: 本番サーバへのデプロイ

on:
  push:
    branches:
      - master

jobs:

  deploy:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1
    - name: バックエンドの準備
      run: composer install
    - name: フロントエンドのビルド
      run: npm install && npm run prod
      env:
        MIX_OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }}
        APP_ENV: "production"
        APP_DEBUG: "false"
        APP_URL: "https://xxxxxx.test"
    - name: 秘密鍵のコピー
      run: echo "$SSH_PRIVATE_KEY" > id_rsa && chmod 600 id_rsa
      env:
        SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
    - name: ファイルをサーバに同期
      run: rsync -rlOtcv --delete --exclude-from=.rsyncignore -e "ssh -i id_rsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" ./ deploy@xxxxxxx.test:/var/www/vhost/xxxxxxxx/
```

このファイルをpushするだけで指定したブランチに更新があった際にワークフローが開始されます。

### ファイルの解説
簡単に定義ファイルの中を見ていきましょう。

#### name: (ワークフローの名称)
```yaml
name: 本番サーバへのデプロイ
```
このワークフローの名前を定義します。GitHub のサイト上から見えるためわかりやすいものにしましょう。日本語も可能です。

#### on: (アクション起動条件)
```yaml
on:
  push:
    branches:
      - master
```
トリガーとなる条件を記述します。ここでは master ブランチに push があった場合の条件を指定しています。

#### runs-on: (コンテナの指定)
```yaml
    runs-on: ubuntu-latest
```
使用するコンテナを指定します。windows や macos も指定できるのでネイティブアプリのビルドなども可能。

#### steps: (アクションの定義)

```yaml
    steps:
```
ここからワークフローをステップごとに定義します。

#### uses: (既存のアクションの使用)
```
    - uses: actions/checkout@v1
```
GitHub Actions は既に用意されたアクションや、[GitHub Marketplace](https://github.com/marketplace?type=actions) に公開されている自作アクションを部分的に使用することができます。ここでは actions/checkout と言うアクションを使用し、リポジトリから最新のソースを clone しています。

#### バックエンドの準備

```yaml
    - name: バックエンドの準備
      run: composer install
```
このステップでは `run` アクションで `composer install` コマンドを実行しています。

#### フロントエンドのビルド
```yaml
    - name: フロントエンドのビルド
      run: npm install && npm run prod
      env:
        MIX_OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }}
        APP_ENV: "production"
        APP_DEBUG: "false"
```
ここではフロントエンドのビルドを実行しています。`run` アクションで `npm install` と `npm run prod` を実行し、Vue.js のソースをトランスパイルしています。
また、前述のトークンや環境ごとの依存情報などは、本来 .env に記述しますが、リポジトリには追加したくないため環境変数経由で Secrets の値を渡しています。


#### 秘密鍵のコピー
Secrets に保存した SSH プライベートキーを環境変数経由でファイルに書き出しています。パーミッションも忘れずに。

```yaml
    - name: 秘密鍵のコピー
      run: echo "$SSH_PRIVATE_KEY" > id_rsa && chmod 600 id_rsa
      env:
        SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
```

#### ファイルをサーバに同期
あとは成果物をサーバに rsync+ssh でコピーするだけです。転送量を節約するため .rsyncignore に不要なファイルを定義し、いくつかのフォルダを除外しています。

```
    - name: ファイルをサーバに同期
      run: rsync -rlOtcv --delete --exclude-from=.rsyncignore -e "ssh -i id_rsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" ./ deploy@xxxxxxx.test:/var/www/vhost/xxxxxxxx/
```

#####.rsyncignore
```
.env
.git
.github
node_modules/
storage/
```

#### いざ実行
ワークフローの実行は `on` で指定したタイミングでの自動起動なので、master ブランチに git push するだけです。
GitHub の Actions タブをクリックすると実行中のタスクをリアルタイムにチェックできます。
![github-actions.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6102/c8fc746f-7d3a-1fd9-f0a0-20645bfe681a.gif)


### まとめ
社内の GitLab では、CI/CD をフル活用しておりユニットテストやLintチェックなどを常に自動化していますが、自社サーバに構築しているので環境を自前で構築する必要があったりスペックに悩まされてきましたが、GitHub Actions ではいとも簡単にできてしまいました。Marketplace での Action も充実してくると思うので今後さらに期待ができますね。非常に楽しみです。