リリース/構成管理: サンプルアプリによるCI/CD環境構築実践 – 「若手エンジニアのためのDevOps入門」(9)

こんにちは、山本和道です。
本記事は連載「若手エンジニアのためのDevOps入門」の第9回です。

第1回 インフラエンジニアにとってのDevOps
第2回 Webアプリでの開発環境構築
第3回 バージョン管理システム
第4回 継続的インテグレーション/デリバリー
第5回 DevOpsのための道具箱: APIを使いこなす
第6回 リリース/構成管理: 概要
第7回 リリース/構成管理: Terraform編
第8回 リリース/構成管理: Ansible/Packer編
第9回 リリース/構成管理: サンプルアプリによるCI/CD環境構築実践

前回はAnsible/Packerを用いて構築したサーバに対するセットアップ(プロビジョニング)を行いました。今回はこれまでの振り返りとしてサンプルアプリケーションを用いて開発〜リリースまでの流れを体験してみます。

今回作成するサンプルアプリケーション

全体構成

今回は開発からリリースまでを体験するために以下のような単純なアプリケーションを開発します。

  • クライアント: Electron
  • サーバ: PHPで実装したAPIサーバ
  • クライアント/サーバのソースコードはGitHubで管理
  • TravisCIでデプロイ(サーバ側のみ)

なお、今回のアプリケーションは開発からリリースまでの流れを体験することに主眼を置いており、各コードについては詳細なエラー処理などを省略し、処理内容を追いやすい形としています。このため、本番環境においては適切ではないコードが含まれる可能性がありますので、参考にする際はご注意ください。

アプリケーション

次に、今回開発するアプリケーションは以下のようなものです。

クライアントはElectronを用います。ボタンを押すとサーバ側で実装されるWeb APIを呼び出すシンプルなものです。アプリケーションを頻繁にリリースするために、新しいバージョンがリリースされたら自動的に更新する仕組みを導入します。

開発からリリースまでの流れ

以下の流れで進めます。

1. 最低限動くクライアント側アプリケーションを実装
2. サーバ側アプリケーションの開発〜配置
3. サーバ側のデプロイの自動化

また、今回は全体の流れを一通り体験するためにできるだけ少ない工程となるようにしています。このため、クライアント側のデプロイの自動化やTravisCI上でのテスト実施などは除外しました。

最低限動くクライアント側アプリケーションの実装

それでは早速開発を行います。最初にクライアント側の実装を行います。Node.js/npmを利用しますのでお手元のマシンにインストールされていない場合はインストールしておいてください。

雛形プロジェクトをクローン

今回は雛形となるプロジェクトを用意しておりますので、これをクローンして変更を加えていく形とします。まずは雛形プロジェクトをクローンし起動してみましょう。

# クローン
git clone https://github.com/yamamoto-febc/devops-example-client-skel devops-example-client
# クローンしたディレクトリへ移動
cd devops-example-client
# 依存ライブラリをインストールして起動
npm install && npm start

起動すると以下のような画面が表示されるはずです。

ボタンを押すと「First release」というテキストが表示されるはずです。後ほどこの処理を変更し、サーバ側へのリクエスト結果を表示するようにします。

これで最低限動くクライアント側アプリケーションの準備ができました。

サーバ側アプリケーションの開発

次にサーバ側アプリケーションの開発を行います。サーバ側はPHPを用い、リクエストがきたら現在時刻を返すだけの単純な機能を実装してみます。

雛形プロジェクトをクローン

サーバ側アプリケーションについても雛形アプリケーションを用意しております。雛形アプリケーションをクローンし起動してみましょう。なお、以下の例ではアプリケーションの起動にDockerを利用していますが、手動で任意のWebサーバ上にアップロードする形でもOKです。

# クローン
git clone https://github.com/yamamoto-febc/devops-example-server-skel devops-example-server
# クローンしたディレクトリへ移動
cd devops-example-server
# DockerでWebサーバ起動(Dockerがない場合は任意のWebサーバへアップロードしてもOK)
docker run -it --rm -p 80:80 -v $PWD:/var/www/html php:7.0-apache

この状態でhttp://localhost/example.phpに対してアクセスすると以下のようなJSONが出力されるはずです。
(Dockerの実行方法によってはlocalhostではない場合もあります)

{"date":"2018-04-25T01:23:45+00:00"}

これでサーバ側アプリケーションの準備ができました。

サーバインフラを構築

次にサーバ側アプリケーションを動かすための環境構築を行います。環境構築には連載第7回で取り上げたTerraformを利用し、さくらのクラウド上にサーバ構築を行います。

準備として以下のドキュメントを参考にTerraformとTerraform for さくらのクラウドのセットアップを行なっておいてください。次に以下のコードを用いてさくらのクラウド上にサーバ構築を行います。

devops-example.tfというファイルと作成し、以下の内容を記載してください。

variable password {}

module "server" {
  source             = "sacloud/server/sakuracloud"
  version            = "0.0.3"
  os_type            = "centos"
  server_name        = "devops-example"
  password           = "${var.password}"
  startup_script_ids = ["${sakuracloud_note.init.id}"]
}

resource "sakuracloud_note" "init" {
  name  = "install-php"
  class = "shell"

  content = << EOF 
#!/bin/sh 

yum update -y 
yum install -y httpd php 
systemctl enable httpd.service 
systemctl start httpd.service 
firewall-cmd --add-service=http --permanent 
firewall-cmd --reload 
EOF 
}

resource "null_resource" "store_private_key" {
  triggers { 
    ssh_key_id = "${module.server.ssh_key_id}" 
  } 
  provisioner "local-exec" { 
    command = "echo '${module.server.ssh_private_key}' > ${path.root}/id_rsa ; chmod 0600 ${path.root}/id_rsa"
  }
  provisioner "local-exec" {
    when    = "destroy"
    command = "rm -f ${path.root}/id_rsa"
  }
}

output ipaddress {
  value = "${module.server.server_ipaddress}"
}

output ssh_private_key {
  value = "${module.server.ssh_private_key}"
}

ファイルを作成したら以下のコマンドを実行して環境構築を行います。

# Terraformの初期化
terraform init
# 適用
terraform apply

なお、apply実行時にパスワードを入力するためのプロンプトが表示されますので、任意の値を入力してください。

apply実行後はカレントディレクトリにSSH接続で用いる秘密鍵ファイル id_rsa が作成されているはずです。また、作成したサーバのグローバルIPアドレスはterraform outputコマンドを実行することで確認できます。後ほど利用しますので忘れずに控えておきましょう。

サーバへのデプロイ

サーバを作成したら次にアプリケーションのデプロイを行います。今回は非常に単純なPHPアプリケーションとなっているため、ソースコードをサーバにアップロードするだけでデプロイ完了です。

以下のコマンドでSCPコマンドを用いてアップロードします。

scp -i <先ほど生成されたid_rsa> <サーバ側アプリケーション>/example.php \
       <サーバのグローバルIPアドレス>:/var/www/html/

デプロイしたらブラウザなどで"http://<サーバのグローバルIPアドレス>/example.php"にアクセスすると以下のようなJSONが表示されるはずです。

{"date":"2018-04-25T01:23:45+00:00"}

クライアント側アプリケーションを修正

サーバが準備できましたので次はクライアント側からサーバに接続する処理を実装します。

クライアント側アプリケーションのrenderer.jsを以下のように修正してください。

const hostname = '<サーバのグローバルIPアドレス>'
const remote = require('electron').remote

document.querySelector('#btn').addEventListener('click', getData);

function getData() {
    const net = remote.net;
    const request = net.request({
        method: 'GET',
        protocol: 'http:',
        hostname: hostname,
        port: 80,
        path: '/example.php'
    })

    request.on('response', (response) => {
        document.querySelector('#result').innerHTML  = ""
        response.on('data', (chunk) => {
            document.querySelector('#result').innerHTML += chunk
        })
    })
    request.on('error', (err) => {
        document.querySelector('#result').innerHTML = `ERROR: ${JSON.stringify(err)}`
    })
    request.end()
}

 

先頭のhostname部分をサーバのグローバルIPアドレスに置き換えておいてください。これでサーバに対してリクエスト(/example.php)を行い、レスポンスを表示してくれるようになります。

TravisCIでデプロイの自動化

ここまでで最低限の機能が出揃いました。しかし、現状ではサーバ側のコードを変更した際は手動でサーバにデプロイしなければなりません。そこで、TravisCIを用いてデプロイを自動化してみます。

今回のアプリケーションは非常に単純なため、TravisCI上からSCPを用いてサーバに直接ソースコードをデプロイする方法とします。TravisCIと連携させるためにサーバ側のソースコードについてはGitHubを用いて管理します。

GitHubのリポジトリ作成とpush

まずはGitHubのリポジトリを作成します。連載第3回などを参考にリポジトリを作成してください。リポジトリ名は"devops-example-server"としてください。

作成したらサーバ側アプリケーションをpushします。以下のコマンドを実行してください。

# gitのremoteに作成したリポジトリを追加
git remote add example git@github.com:<アカウント名>/devops-example-server.git
# push実行
git push -u example master

このリポジトリに対してpush時にTravisCIでデプロイを行うようにします。リポジトリへのpush後はTravisCIの管理画面からこのリポジトリとの連携をONに設定しておいてください。

TravisCI用の設定ファイル作成

次にTravisCIからデプロイするための設定ファイルを記載します。サーバ側アプリケーションのルートディレクトリに".travis.yml"という名前のファイルを以下の内容で作成します。

language: php
script:
- echo "Start CI"
deploy:
  provider: script
  script:
  - bash deploy.sh
  skip_cleanup: true
  on:
    branch: master

次にデプロイ用のスクリプトを"deploy.sh"という名前で以下の内容で作成します。

#!/bin/sh

chmod 0600 id_rsa
scp -q -o "StrictHostKeyChecking no" -i id_rsa *.php root@$REMOTE_HOST:/var/www/html/

masterブランチの変更を検知すると、scpコマンドを用いて拡張子が.phpのファイルをサーバにアップロードするという処理になっています。

次にscp時に利用する宛先グローバルIPアドレスや秘密鍵の指定を行います。これらは暗号化して".travis.yml"ファイルに記載します。

準備として暗号化した値を".travis.yml"に記載するためにTravisCIのCLIをインストールしておきます。以下のコマンドでインストールを行なってください。(rubyが必要です)

gem install travis -v 1.8.8 --no-rdoc --no-ri

インストール後はloginサブコマンドを実行してログインしておく必要があります。

travis login

続いて宛先グローバルIPアドレスを暗号化して指定します。このコマンドを実行すると.travis.ymlファイルに暗号化した内容が追記され、環境変数として参照可能になります。

travis encrypt REMOTE_HOST=<サーバのグローバルIPアドレス> -a

次に秘密鍵を暗号化して指定します。秘密鍵はファイルのまま扱いたいのでencrypt_fileサブコマンドを利用します。

travis encrypt-file <秘密鍵ファイル> -w id_rsa -a

こちらも同じく.travis.ymlファイルに変更内容が追記されます。

これで準備完了です。.travis.ymlファイルをコミットし、GitHubへpushしましょう。この時に秘密鍵ファイルをコミットしてしまわないように注意してください。

サーバ側アプリケーションを更新して反映されることを確認

では自動化できているか確認してみましょう。
サーバ側アプリケーションのexample.phpを以下のように変更してみましょう。

<?php
  date_default_timezone_set("UTC");
  $now = new DateTime();
  $data = array(
      // ISO8601 format
      'date'=> $now->format("c"),
      'msg' => "Updated"  // この行を追加
  );
  print(json_encode($data));
?>

変更後はコミットしてGitHubへpushするとTravisCI上でデプロイが行われます。

デプロイ後にクライアント側アプリケーションを起動しボタンをクリックすると変更されていることが確認できるはずです。

今回はmasterブランチに直接コミットしましたが、別途ブランチを作成してプルリクエストを作成、その後レビューが通ったらmasterブランチへマージするという流れを取ることも可能です。この場合masterブランチへのマージのタイミングでデプロイが行われることになります。

例えばテストを追加してプルリクエスト作成時にTravisCI上でテストを実行、テストが通らなければマージできないという設定を行うことも可能ですし、その後プルリクエストのレビューのためにステージング環境を立てそちらにデプロイ、マージされたらステージング環境を破棄するといった運用も考えられます。今回の例を土台に各自で修正して動かしてみてください。

まとめ

今回はElectronを用いたアプリケーションの開発を通して、これまでの連載で扱った様々なテクニックを具体的にどう利用するのかを体験してみました。

Terraformでのサーバ構成の自動化や、GitHub/TravisCIを用いてのリリースの自動化などは、DevOpsを進める上で便利に使える武器となりますので、ぜひ実際に手を動かしてみて、可能であれば自分なりのアレンジを加えてみるのがオススメです。

最後に今回作成したアプリケーションの雛形を公開しているURLを再掲しておきます。

今回の構成を元に、クライアント側のリリースを自動化したり、プルリクエストを受け取ったらステージング環境にデプロイしたりするといった応用にもぜひチャレンジしてください。

次回はDevOpsのための道具箱として、DevOpsの各プロセスを結びつけるためのBotや連携サービスといったツールについて扱う予定です。

以上です。