まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:ci

自分で開発したウェブサービスの公開先として、最近は dokku で作った公開プライベート(=自分専用)クラウド環境が重宝しています。デプロイはソースコードを git clone(pull) して git push するだけ、という非常に簡略化されていることに加え、やはり「自分専用(=使いたいサブドメインを誰かに先に取られている心配がない)」という気持ちの余裕も以外と大きい気がしています。dokku の導入については過去の記事を参照してください:
dokku でプライベート PaaS 環境を構築する(1)
dokku でプライベート PaaS 環境を構築する(2)


今回は dokku のいわゆる CI/CD(Continuous Integration / Continuous Delivery) についてブログエントリを書きます。dokku が非常に便利であるが故に、ソースコードが更新されるたびに手作業で push するのは(大した手間ではないのですが)やはり面倒です。というわけで Git リポジトリへのプッシュをフックとする dokku のデプロイ自動化を実現する方法を調べてみました。特に今回紹介するのは GitHub Actions を使った方法で、ソースコードを GitHub リポジトリで管理している人であればちょっとした設定を追加するだけで CI が実現できます。なお、今回 CI/CD を設定する dokku サーバーは "yellowmix.net" というカスタムドメインを有効に設定してあって、"XXXXX.yellowmix.net" というホスト名でアプリケーションをデプロイできるように設定済みである、という前提で紹介します(手順3の中にこの設定に関わる部分があります)。


以下、その手順紹介です。


【手順1 パスフレーズなしの秘密鍵/公開鍵を作成・登録】
GitHub Actions 内でデプロイを自動実行するため、パスフレーズ無しで作られた秘密鍵が必要です。というわけで、パスフレーズ有りの秘密鍵を使っている場合は作成および登録の再実行が必要になります。

まずは秘密鍵と公開鍵を(再)作成します。dokku サーバーのシェルにログインして、以下のコマンドを実行します:
$ ssh-keygen

この後、秘密鍵を作成するパスを入力するよう促されます。デフォルトでは "~/.ssh/id_rsa" が指定されているはずです。変更する場合は正しいファイルパスを入力、デフォルト設定のままで問題なければそのまま Enter を入力します。

次にファイルのパスフレーズを入力するよう促されますが、今回はパスフレーズのない秘密鍵および公開鍵のペアを作りたいので、空のまま Enter を押して先に進めます。パスフレーズの指定が終わると指定されたパスで秘密鍵および公開鍵が作成されます。特に秘密鍵の内容は手順2でも必要になるので、 cat コマンドで一度中身を確認しておきます("-----BEGIN RSA PRIVATE KEY-----" で始まって "-----END RSA PRIVATE KEY-----" で終わる内容が表示されます):
$ cat ~/.ssh/id_rsa

秘密鍵が作れたら dokku に登録します。上述のセットアップを既に別の(パスフレーズ付きの)秘密鍵で実施済みで、別の秘密鍵が登録済みの場合は、以下のコマンドで一度削除しておきます:
$ sudo dokku ssh-keys:remove admin

改めて(再度)秘密鍵をファイルパスを指定して登録します:
$ cat ~/.ssh/id_rsa | sudo dokku ssh-keys:add admin

これで新しい(パスフレーズのない)秘密鍵が作成され、 dokku に登録されました。続いて GitHub のリポジトリ側で GitHub Actions 側の設定を行います。


【手順2 GitHub の対象アプリケーションに GitHub Actions 用の秘密鍵を設定】
GitHub リポジトリにコミットされたタイミングでデプロイを実行できるよう、対象アプリケーションの GitHub リポジトリに GitHub Actions を設定します。まずは GitHub の対象アプリケーションのページを開き、Settings を選択します(ユーザーの Settings ではなく、アプリケーションの Settings を開きます):
2022060901


CI ワークフローを定義する前に、ワークフロー内で使う秘密鍵の情報を先に登録しておきます。Settings ページの左ペインから Secrets - Actions を選択します:
2022060902


新規に秘密鍵を登録したいので、画面右上の "New repository secret" ボタンをクリックします:
2022060903


秘密鍵を登録する画面が表示されるので、NAME 欄に SSH_PRIVATE_KEY と入力し、Value 欄には手順1で作成した秘密鍵ファイルの内容("-----BEGIN RSA PRIVATE KEY-----" から "-----END RSA PRIVATE KEY-----" まで)をそのままコピー&ペーストして入力し、最後に "Add secret" ボタンをクリックします:
2022060904


以下のように表示されればリポジトリへの秘密鍵の登録は完了です:
2022060905



【手順3 対象アプリケーションに GitHub Actions を設定】
ここまでの準備ができていれば、後はコミット時(プッシュ時)の GitHub Actions を登録することで dokku への自動デプロイを行うことができるようになります。ソースコードに .github/workflows/ というフォルダを追加し、このフォルダ内に以下の内容の deploy.yml ファイルを追加します:
---
name: 'deploy'

# yamllint disable-line rule:truthy
on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Cloning repo
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Push to dokku
        uses: dokku/github-action@master
        with:
          # specify the `main` branch as the remote branch to push to
          branch: 'main'
          git_remote_url: 'ssh://dokku@yellowmix.net:22/hostname'
          ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}

このうち、青字部分(main と書かれた部分)は「どのブランチが更新された時に、どのブランチを対象にデプロイするか」を指定しています。今回は「 main ブランチ更新時に main ブランチの内容をデプロイ」したいので、このような設定にしています。master ブランチを使いたい場合はここを master に変更してください。 また赤字部分はデプロイ先のリポジトリ URL です。この例では "yellowmix.net" というドメインホストを使って https://hostname.yellowmix.net/ という URL でアプリケーションを公開するための設定にしています。この部分は皆さんの dokku 環境に合わせて変更する必要があるのでご注意ください。 加えて、最終行に手順2で設定した秘密鍵を使う指定がされています。秘密鍵は外部に漏れてはまずい情報なので、ハードコートや公開変数などは使わずに、このような形で登録済みのシークレット情報を参照するよう指定しています。

なお、この deploy.yml ファイルの中で dokku/github-action というリポジトリが指定されている箇所がありますが、これは dokku から提供されている GitHub Actions 連携用のコンテナイメージです。このコンテナイメージを使ってデプロイを行う、という作業内容になっています。同コンテナイメージのソースコードはこちらで提供されています:


ここまで用意できたら、対象のプロジェクトを GitHub にコミット&プッシュします(以下の例は main ブランチに直接コミットしている想定です):
$ git add .

$ git commit -m 'node v14'

$ git push origin main

プッシュが成功すると GitHub リポジトリで GitHub Actions が起動するはずです。起動の様子はリポジトリ内の "Actions" メニューから確認できます(画面は Actions が実行中の様子):
2022060906


実行が(成功か失敗かで)完了すると、その結果が表示されます(この例では緑マークが付いているので成功しています。失敗は赤):
2022060907


成功・失敗に関係なく、Actions 名部分をクリックすると、Actions の実行ログを見ることができます。特に失敗した場合などはこのログが原因を調べるヒントになっている可能性が高いので、失敗に終わった場合はまずログを参照することになると思います:
2022060908


GitHub Actions が成功していた場合、更新されたアプリケーションが指定 URL で稼働できているはずです。実際にアクセスして動作確認してみましょう:
2022060900


以上、GitHub Actions を使って dokku にアプリケーションを自動デプロイするための設定を紹介しました。dokku は専用のリポジトリに git push するだけでもデプロイが出来て便利ですが、一度 CI/CD の便利さを知ってしまうと「わざわざデプロイする手間が面倒」に感じられてしまうほど便利なので、今後 dokku を使って動かすアプリについては全てこの設定を有効にしてもいいかな、と感じています。


 

GitHub が提供をはじめた CI/CD 機能である GitHub Actions を使って IBM Cloud の IKS(IBM Kubernetes Services) にアプリケーションをデプロイできることを確認したので、一連の手順を紹介します。


【何をする?】
簡単に言うと、GitHub のリポジトリにアプリケーションのソースコードをコミット&プッシュすると、その最新コードのアプリケーションが IBM Cloud 内の Kubernetes サービスへ自動デプロイされて公開される、ということを GitHub 内の機能だけで実現します。

なお、GitHub Actions は GitHub の無料アカウントで使える機能であることに加え、IBM Cloud の IKS も(クレジットカード登録が必要な BASIC アカウントに切り替える必要はありますが)Kubernetes のシングルワーカーノードが 30 日間無料で利用できます。 以下に紹介する内容は全て無料で試すことができる内容です。

では以下に IKS の準備、GitHub の準備に続けて実際にアプリケーションコードをコミットして IKS 上で動かす所までの手順を紹介します。


【IKS の準備】
IBM Cloud で IKS サービスを作成します。IBM Cloud にログインします。ライトアカウントの場合は IKS が利用できないため、クレジットカードを登録して BASIC アカウントに切り替えるか、または新たにアカウントを作成して BASIC アカウントに切り替える必要があります。

BASIC アカウントでログイン後、画面右上の「リソースの作成」ボタンをクリックします:
2020052701


作成するサービスを一覧から選択します。画面左メニューから「サービス」-「コンピュート」を選択し、画面右の一覧から "Kubernetes Service(以下 IKS)" を選択します:
2020052702


IKS は有償版と無償版(30日経過後に削除)があります。以下は無償版である「無料クラスター」を選択している想定で紹介を続けます。クラスター名は "mycluster" 、リソースグループは "Default"(いずれも既定値)として画面右の「作成」ボタンをクリックします:
2020052703


ここから IKS 環境の作成が始まります。操作できる状態になるまでしばらく(数10分程度)かかります:
2020052704


この IKS の準備をしている時間を使って、この後の作業で利用する API キーを用意しておきます。画面右上のメニューから「管理」を選び、「アクセス(IAM)」を右クリックしてリンクを新しいタブで開く形で(つまり別ウィンドウや別タブで開く形で)選択します:
2020052706


別ウィンドウでアクセス設定画面が開いたら、画面左メニューで「API キー」を選び、「IBM Cloud API キーの作成」ボタンをクリックします:
2020052707


ダイアログが表示されるので API キーの名称(任意ですが、例えば "API Key for GitHub Actions" など)を入力して「作成」します:
2020052708


正しく実行できると以下のような画面になります。作成された API キーはマスクされ、表示されていません。この画面をこのまま閉じてしまうと作成された API キーの内容を再度確認することはできなくなるため、ファイルでダウンロードしておくか、クリップボードにコピーしておくか、目のアイコンをクリックして、マスクを外して表示し、その内容をどこかに保存しておく必要があります:
2020052709


目のアイコンをクリックするとマスクがはずれ、API キーの値を確認することができます。繰り返しますが、このダイアログを消すと API キーを再確認することはできません。忘れてしまった場合は再度 API キーを新規作成する必要がある点に注意してください。なんらかの方法で API キーの内容を再確認できるようこの内容を保存しておきます(後で使います):
2020052710


あらためて IKS サービスの作成状況を確認しておきましょう。「ファイナライズ中」と出ていればあと少しです。。。:
2020052711


しばらく待って全ての準備が完了すると、下図のように「通常」というステータスに変わります。この状態になっていれば IKS 上にアプリケーションをデプロイするための準備が整ったことになります:
2020052712


この画面を離れる前に Web 端末の準備もしておきます。この IKS で用意する Kubernetes 環境はローカルマシンの kubectl などから操作することも可能ですが、kubectl の環境を持っていない人でもブラウザ画面から利用できるターミナル機能が用意されており、こちらを使うことでローカルインストールや環境設定も不要で利用することができるようになって便利です。

Web 端末を利用するには IKS 画面右上の「Actions...」と書かれた箇所をクリックし、メニューから「Web 端末」を選択します:
2020052705


初めてこの操作をした場合はインストールができていないため以下のようなダイアログが表示されます。「インストール」をクリックして数分お待ち下さい:
2020052713


Web 端末のインストールが出来た後に改めて「Actions...」-「Web 端末」を選択すると、画面内にターミナル機能が現れ、ここから kubectl コマンドや IBM Cloud への命令を実行する ibmcloud コマンドを利用することができるます。画面が小さくて不便な場合は(別タブに)最大化して利用することもできます:
2020052714


この Web 端末を使って IBM Cloud CR(Container Registry) の名前空間をセットアップしておきます(後で使います)。まず "ibmcloud login" と入力します。IBM ID とパスワードの入力が促されるので入力し、Web 端末で IBM Cloud にログインします:
$ ibmcloud login

一度この時点で CR の名前空間一覧を確認しておきます。確認するためのコマンドは "ibmcloud cr namespaces" で、初めて実行した場合は1つも定義されていないという結果となるはずです:
$ ibmcloud cr namespaces

改めて名前空間を作成します。ユニークな名前空間名を指定して "ibmcloud cr namespace-add (名前空間)" を実行します:
$ ibmcloud cr namespace-add (名前空間名)

名前空間名は他の人が使っているものを指定することはできません(エラーとなります)。自分の場合は "ibmcloud cr namespace dotnsf-ns" と入力しました。実際に実行する場合はここで他の人が使っていないものを指定して実行してください(エラーとなる場合はエラーにならずに実行完了するまで繰り返して実行してください)。

最後に CR の名前一覧表示コマンドを再度実行し、直前のコマンドで作成した名前空間が一覧に含まれていることを確認してください:
$ ibmcloud cr namespaces

自分の上記例ではこんな感じになりました。この名前空間名も後で利用します:

2020052701


IKS の準備作業はこれで終わりです。


【アプリケーションおよび GitHub の準備】
次に IKS にデプロイするアプリケーションと GitHub 側の準備を行います。まずはデプロイするアプリケーションを準備して Docker 対応(Dockerfile の準備)します。ここは実際に IKS にデプロイして使ってみたいアプリがある場合は個別に用意していただいても構いません。

一応サンプルとしてシンプルな Web アプリケーションを用意しました。自分でアプリケーションを用意しない場合はこちらをお使いください:
https://github.com/dotnsf/hostname_githubactions_iks


このサンプルを使う場合は、まず GitHub にログインした上で上記 URL を開きます。そして画面右上の "fork" を選択し、自分自身のリポジトリとして複製してください:
2020052702


成功すると https://github.com/XXXXX/hostname_githubactions_iks というリポジトリができあがります(XXXXX 部分は各ユーザー個別の ID 名)。以降はこのリポジトリを使って GitHub Actions を利用します。まずこの URL を指定してリポジトリの内容をローカルに clone しておきます(XXXXX 部分は自分の ID に置き換えて実行してください):
$ git clone https://github.com/XXXXX/hostname_githubactions_iks

これでサンプルアプリケーションのソースコードがローカルファイルとしてクローンされ、ローカルファイルとして参照することができるようになりました。簡単にアプリケーションの内容を紹介しておくと、このサンプルアプリケーションは Node.js で実装されており、 GET / という HTTP リクエストに対して /etc/hostname ファイルの内容を text/plain でそのまま返すだけの機能を持っています。本体である app.js ファイルの全容は以下(これで全部)で、実質20行足らずの実装です。またポート番号は 8080 番で固定しています:
//.  app.js
var express = require( 'express' ),
    fs = require( 'fs' ),
    app = express();

app.get( '/', function( req, res ){
  res.contentType( 'text/plain; charset=utf-8' );
  fs.readFile( '/etc/hostname', "utf-8", function( err, text ){
    if( err ){
      res.write( JSON.stringify( err, 2, null ) );
      res.end();
    }else{
      res.write( text );
      res.end();
    }
  });
});

var port = 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );


この app.js と、依存ライブラリや起動コマンドが記述された package.json や、コンテナ対応のための Dockerfile などが含まれたアプリケーションとなっています。

繰り返しますが、実際に IKS にデプロイするアプリケーションは Docker 対応できていれば他のものを使っても構いません。ただしその場合はこのサンプルの .github/ フォルダ以下をまるごとそのアプリケーションプロジェクトにコピーしておいてください(実質的に .github/workflows/iks.yml ファイル1つだけのフォルダで、この iks.yml が Github Actions を IKS で使うためのワークフローを定義しています)。

iks.yml ファイルの中身はこの後で参照しますが、このファイルは Github のプロジェクトのシークレット情報を参照して動くよう定義されています。したがって必要なシークレット情報を Github プロジェクト内にあらかじめ定義しておく必要があります。

そのための設定を行います。 https://github.com/XXXXX/hostname_githubactions_iks を開き、"Settings" タブを選んで左メニューから "Secrets" を選択します。このプロジェクトに設定されたシークレット情報の一覧が表示されますが、最初は空のはずです。ここにシークレット情報を追加するため "New secret" ボタンをクリックします:
2020052703


シークレット情報は名前(Name)と値(Value)の組で定義します。まずは "IBM_CLOUD_API_KEY" という名前のシークレットを定義します。この値としては上述した IBM Cloud 利用時に作成してダウンロードするかクリップボードに保存した IBM Cloud API Key の値を入力します。入力後に "Add secret" ボタンをクリックして保存します:
2020052704


同様にしてもう1つ、"ICR_NAMESPACE" という名前で、 IBM Cloud CR の名前空間として作成した名称(上記例では dotnsf-ns ですが、個別に作成した時の値)を入力し、最後に "Add secret" をクリックします:
2020052705


つまりプロジェクトのシークレット情報として IBM_CLOUD_API_KEY と ICR_NAMESPACE の2つが定義されている状態にします。これで GitHub プロジェクト側の準備は完了です:
2020052706


最後に Github Actions のワークフロー定義を各自の内容に合わせて書き換えます。プロジェクト内の .github/workflows/iks.yml ファイルをテキストエディタで開きます。なおこのファイルの内容は以下のプロジェクトに含まれて提供されていたものをベースにしています:

https://github.com/IBM/actions-ibmcloud-iks


iks.yml を開き、20 行目以下(env: で始まる行以下)の値を編集します。といっても自分の独自アプリケーションではなく https://github.com/dotnsf/hostname_githubactions_iks からフォークしたプロジェクトのアプリケーションを使う場合はほぼそのままでも動くはずです:
2020052707


編集の必要な箇所があるとすれば以下です:
変数名設定内容既定値
IBM_CLOUD_REGIONIBM Cloud で利用する IKS のリージョン(地域)us-south
REGISTRY_HOSTNAME内部で利用する Docker Hub のホスト名us.icr.io
IMAGE_NAMEDocker イメージを保存する時の名称hostname
IKS_CLUSTERIKS 作成時に指定したクラスタ名mycluster
DEPLOYMENT_NAMEDocker イメージを IKS にデプロイする時の DEPLOYMENT の名称hostname
PORTアプリケーションが HTTP アクセス時に LISTEN するポートの番号
8080


ここまでの作業で hostname_githubactions_iks プロジェクト(Github にコミットするプロジェクト)のファイルに変更を加えている場合はこれで準備完了です。加えていない場合はコミットに変化を加えるというだけの目的でいずれかのファイルに挙動に支障のない変更を加えておいてください。例えば README.md ファイルの最後に空の1行を追加する、などでも構いません(苦笑):
2020052708


これで全ての事前準備が整いました。


【GitHub Actions の実行】
ではこのプロジェクトを Github にコミット&プッシュします:
$ cd hostname_githubactions_iks

$ git add .

$ git commit -m 'README.md updated.'

$ git push


Push の成功と同時に Github Actions が実行されます。リポジトリのページ(https://github.com/XXXXX/hostname_githubactions_iks )を開き、"Actions" タブを選択すると、コミットコメントの横にクルクル回るアイコンが表示され、Github Actions に定義された内容(iks.yml の内容)に従ってアプリケーションイメージが内部 Docker Hub に登録され、そこから IKS へデプロイされていきます:
2020052709


しばらくすると一連の作業が完了します。下図のように緑のチェックマークが表示されていればコマンドが成功しています(赤いバツは失敗):
2020052710


この箇所をクリックすることでワークフローで実行された内容の詳細を確認したり、(失敗している場合は)どこで失敗しているかを確認することもできます:
2020052700


この時点でアプリケーションは IKS 内にデプロイされ動いており、パブリックに公開されています。では実際に動いているアプリケーションにアクセスしてみたいのですが、そのためにはアクセスするための情報(IP アドレスおよびポート番号)を見つける必要があります。

まず公開されているパブリック IP アドレスは IBM Cloud ログイン後のダッシュボードから確認することができます。作成した IKS のワーカーノードを表示し、稼働しているサービスのパブリック IP を確認します。これが外部アクセス用の公開 IP アドレスとなっています:
2020052701


またポート番号は Web 端末から "kubectl get svc" を実行し、サービス名(iks.yml で指定したデプロイ名、上述の例だと hostname)の PORT(S) 列を参照します。 "80:*****/TCP" と表示されている ***** 部分(下図の場合は 32284)がサービスにアクセスするためのポート番号となります:
2020052702


これらを組み合わせてウェブブラウザまたは curl コマンドなどで http://(IP アドレス):(ポート番号) にアクセスします。下図の例では http://173.193.112.74:32284/ へアクセスしています。またその実行結果として(コンテナの /etc/hostname の中身である hostname-59cb7b958f-lwv52 という値が表示されています。この結果は実行環境によって異なりますが、実際に稼働しているコンテナから取得した値となっています)。実際の稼働環境にアクセスして挙動を確認することもできました:
2020052703


この時点で IKS 上で稼働しているので、インスタンスのスケールイン/スケールアウトといった操作も可能です。

またこの状態から更にアプリケーションのソースコードを改良するなどした上で、再度 git commit & git push すると同じワークフロープロセスが動き、自動的に IKS 内に最新コードのアプリケーションがデプロイされる、という CI/CD 環境が構築できました。


【デプロイしたポッド等を削除する場合】
最後に環境を削除する手順を紹介します。IKS そのものごと削除する場合は(IBM Cloud のメニューから IKS ごと削除すればよいので)ある意味で簡単ですが、IKS を残して IKS 内にデプロイしたアプリケーション環境を削除する場合の手順を紹介します。

まず Web 端末で "kubectl get all" を実行して、ポッドやサービス、デプロイメントの情報を確認します:
2020052704


この実行結果から hostname 関連のポッド、サービス、デプロイメントを削除します。Web 端末で続けて以下のコマンドを実行します(service/kubernetes のサービスは IKS そのものなので削除しないよう気をつけてください):
$ kubectl delete deployment hostname

$ kubectl delete service hostname

$ kubectl delete pod hostname-59cb7b958f-lwv52

最後にもう一度 "kubectl get all" を実行して、hostname 関連のものが残っていないことを確認します:
2020052705


上記のような結果になれば無事に削除できました。
 

このページのトップヘ