= GitHubで回すWebサイト制作 = * 思えば、Webサイト制作の現場は、下記のような移り変わりであった気がする。 * エンジニアが作業した(場合によってはHTMLも書く)。 * デザイナーさんが作業するようになった(ちょっとはサーバー側の事情も考慮してくれる)。 * 既存のデザインに対して営業さんがちょこっと修正するようになった(今回の動機)。 * エンジニア・デザイナーさんであれば、FTP(今時はSFTP/SCP)使ってサーバーにアップロードしてくれた。あるいは強制できた。 * 流石に営業さんにそういうツールを使ってもらうのは通じず。 * そのために、CSM(`Content Management System`)導入することもしばしば。 * しかし世の中にはもっとカジュアル(HTML一枚程度)にWebサイト運用したいこともしばしば。 * 今回、そんなライトな要求に対して!GitHubで回す&自動デプロイを実施するための仕組みを作ってみた。 <> == 検証環境 == * 下記ソフトウェアの利用を前提に検証を実施した。いずれも現時点で最新のリリースに基づいて検証しているが、ある程度古い環境、より新しい環境でも問題無いと思われる。 * ただし、OSについては古いOS(CentOS6等)ではこの手順では実施できない点に注意。 * 上記以外の環境では、以下の点に相違が発生する。当該環境に応じて読み替えたし。 * インストール方法 * インストールされるディレクトリ * 逆に以下の点は参考にできる。 * 設定パラメータとその意味 * 運用事例 * ただし、[[https://snapcraft.io/|スナップクラフト]]固有の問題を回避するためのワークアラウンドだけは別で。 ||<#FFFF00> 用途 ||<#FFFF00> ソフトウェア名 ||<#FFFF00> バージョン番号 ||<#FFFF00> 備考 || || OS || `CentOS 7` || `7.7.1908` || SELinuxは有効とする || || Webサーバー || `Apache 2.4` || `2.4.41` || [[https://ius.io/|IUSリポジトリ]] || || Webフック || [[https://github.com/adnanh/webhook|webhook]] || `2.6.10` || [[https://snapcraft.io/|スナップクラフト]] || || ファイルフック || incron || `0.5.12` || ワークアラウンドのために泣く泣く使用 || == 検証目標 == * !GitHubに対して以下の設定を行う。 * WebフックのURLの指定 * シークレットの指定 * デプロイ用のSSH鍵の設置 * 上記設定のためにサーバー側のセットアップを行う。 * 前提として、運用するサイトを「`https://www.example.jp/`」とし、WebフックのURLを「`https://www.example.jp/hooks/update-contents`」とする。 * もちろんWebフックを受けて実際にデプロイを実施する。 * なおApacheの細かい設定については説明しない。 * 今回のシステムは下記のようなフローとなる。 {{attachment:apache-webhook.svg||text-align="center",width=75%}} = ミドルウェアのインストール = == OS == 基本的な(とは何かは議論しない)セットアップは実施済みであることとする。 === ファイアウォール設定 === {{{#!highlight shell numbers=disable sudo firewall-cmd --add-service=http --permanent sudo firewall-cmd --add-service=https --permanent sudo firewall-cmd --reload }}} ※ここではHTTPは80番ポート、HTTPSは443番ポートを使用するモノとして記述しておく。 {{{#!highlight shell numbers=disable firewall-cmd --permanent --service=http --get-ports firewall-cmd --permanent --service=https --get-ports }}} 違うポート番号で運用する場合は `--get-ports` の代わりに `--add-port` と `--remove-port` を駆使して設定すること。 === SELinux設定 === 特に設定無し。下記コマンドを実行した結果で得られたポート範疇内の設定であれば問題無し。 {{{#!highlight shell numbers=disable semanage port -l | grep -w http_port_t }}} 違うポートで運用する場合は下記コマンドを実行する(例では81番ポートを追加してみた)。 {{{#!highlight shell numbers=disable sudo semanage port -a -t http_port_t -p tcp 81 }}} == Apache == {{{#!highlight shell numbers=disable sudo yum -y install https://centos7.iuscommunity.org/ius-release.rpm sudo yum -y install httpd24u httpd24u-mod_ssl httpd24u-tools }}} ※インストールした直後は自動起動設定を有効にしない、Apacheを立ち上げない。 == InCron == {{{#!highlight shell numbers=disable sudo yum -y install epel-release sudo yum -y install incron sudo systemctl enable incrond sudo systemctl start incrond }}} == スナップクラフト == {{{#!highlight shell numbers=disable sudo yum -y install snapd sudo systemctl enable --now snapd.socket sudo ln -s /var/lib/snapd/snap /snap sudo systemctl reboot }}} ※スナップクラフトの有効化のために一度再起動しておくこと。 == Webhook == {{{#!highlight shell numbers=disable sudo snap install webhook }}} ※スナップクラフトが稼働してる状態で上記コマンドを実行すること。 === /etc/systemd/system/snap.webhook.webhook.service.d/override.conf === {{{#!highlight shell numbers=disable sudo systemctl edit snap.webhook.webhook.service }}} 上記コマンドを実行して以下の設定を行う。 {{{ [Service] EnvironmentFile=-/etc/sysconfig/webhook ExecStart= ExecStart=/usr/bin/snap run webhook $OPTIONS }}} ※Webhook起動のための設定オプションの指定ができなかったので、`/etc/sysconfig/webhook` を参照するように改変してみた。 === /etc/sysconfig/webhook === {{{#!highlight shell numbers=disable OPTIONS="-hooks /etc/httpd/conf/hooks.yaml -hotreload -ip 127.0.0.1 -port 9000" }}} ※動作確認が取れるまで `-verbose` オプションを指定しておいた方がいいかもしれない。 == 後始末 == {{{#!highlight shell numbers=disable restorecon -RFv /etc/sysconfig /etc/systemd/system }}} = 自作スクリプトの設置 = == ディレクトリ作成 == {{{#!highlight shell numbers=disable sudo mkdir -p /root/.webhook/bin sudo mkdir -p /root/.webhook/run sudo mkdir -p /etc/httpd/libexec }}} == /root/.webhook/run/activate_id_update-contents == 以下のコマンドを実行してファイルを作成する。 {{{#!highlight shell numbers=disable sudo touch /root/.webhook/run/activate_id_update-contents }}} == /root/.webhook/bin/fecho == {{{#!highlight shell numbers=disable #!/bin/sh : APPEND=no : OUTFILE=- while getopts ao: OPT; do case $OPT in a) APPEND=yes ;; o) OUTFILE=$OPTARG ;; \?) continue ;; esac done shift $((OPTIND - 1)) if [ x"$OUTFILE" = x"" -o x"$OUTFILE" = x"-" ]; then echo $* else if [ x"$APPEND" = x"yes" ]; then echo $* >> "$OUTFILE" else echo $* > "$OUTFILE" fi fi }}} == /etc/httpd/libexec/update-contents.sh == {{{#!highlight shell numbers=disable #!/bin/sh cd /var/www/html git pull -q if [ x"$1" = x"refs/heads/master" ]; then git checkout -qf master fi restorecon -RF . }}} == 後始末 == {{{#!highlight shell numbers=disable sudo restorecon -RFv /root/.webhook /etc/httpd/libexec sudo chmod 0755 /root/.webhook/bin/fecho /etc/httpd/libexec/update-contents.sh }}} = ミドルウェアの設定 = == /etc/incron.d/update-contents == {{{#!highlight text numbers=disable /root/.webhook/run/activate_id_update-contents IN_CLOSE_WRITE,loopable=false /etc/httpd/libexec/update-contents.sh $$(cat $@) }}} == /etc/httpd/conf/httpd.conf == {{{#!highlight apacheconf numbers=disable ProxyPass /hooks/ http://localhost:9000/hooks/ Require all granted Require all denied Require all denied }}} * もちろん、上記設定は `` ~ `` 中で設定してもよい。 * またセキュリティ上の観点から、Gitに関する管理ディレクトリへのアクセスを制限しよう。 * `` ~ `` で指定するパスは絶対パスなので、ドキュメントルートを変える場合は、適宜合わせること。 == /etc/httpd/conf/hooks.yaml == {{{#!highlight yaml numbers=disable - id: update-contents command-working-directory: "/root" execute-command: "/root/.webhook/bin/fecho" pass-arguments-to-command: - source: string name: "-o" - source: string name: "/root/.webhook/run/activate_id_update-contents" - source: payload name: ref response-message: | Updated, Done. trigger-rule-mismatch-http-response-code: 400 trigger-rule: and: - match: type: payload-hash-sha1 secret: "GitHubに設定するシークレット" parameter: source: header name: "X-Hub-Signature" - or: - match: type: value value: ping parameter: source: header name: "X-GitHub-Event" - match: type: value value: push parameter: source: header name: "X-GitHub-Event" }}} == 後始末 == {{{#!highlight shell numbers=disable sudo restorecon -RFv /etc/incron.d /etc/httpd/conf }}} == デプロイ用SSH鍵の準備 == {{{#!highlight shell numbers=disable sudo ssh-keygen -t ssh-ed25519 -C root@$(hostname) -N "" -f /root/.ssh/id_ed25519 sudo restorecon -RFv /root/.ssh }}} 以下の点に注意すること。 * `root` 鍵が必要とするため注意。 * パスフレーズの設定は行わない。 * なお今回なうでやんぐな意識高い系のためにED25519鍵(253ビット)を採用した。 = GitHubの設定 = == まずはSettings == リポジトリのトップを見ると、下記のようなメニュー(タブ)が表示されているはず。 {{attachment:settings.png||align="center",width="75%"}} この時、赤丸で強調した「`Settings`」タブが表示されない場合は、設定すべき権限が無いので、権限をもらおう。 もちろん他人のリポジトリの権限くれ、と言っても極めて困難と思われるので、その辺りの調整についてはここでは説明しない。 ここでは「`Settings`」タブが表示されている前提で説明を行う。 == Deploy Keys == {{attachment:deploy-keys.png||align="center",width="20%"}} == Webhooks == {{attachment:webhooks.png||align="left",width="15%"}} ||<#FFFF00> 設定項目 ||<#FFFF00> 設定内容(例) || || ペイロードURL || `https://www.example.jp/hooks/update-contents` を入力する || || `Content-Type` || `application/json` を選ぶ || || シークレット || 「!GitHubに設定するシークレット」を入力する || || `SSL verification` || `Enable SSL verification` を選ぶ || || `Which events would you like to trigger this webhook?` || `Just the push event.` にチェックを入れる || || `Active` || チェックを入れる || 上記入力して保存します。 = コンテンツの展開方法 = !GitHubの設定まで完了したらコンテンツを展開します。 {{{#!highlight shell numbers=disable env - git clone --no-checkout git@github.com:example-jp/www.example.jp.git /var/www/html cd /var/www/html git config core.sparseCheckout true echo "/*\n\!README.md" > .git/info/sparse-checkout git checkout restorecon -RFv /var/www/html }}} * ※`README.md`はチェックアウトの対象外にしましょう。通常これはコンテンツ関係者向けの説明になります。 * なお自分は `/var/www/www.example.jp` に展開するのが好みです。当然ドキュメントルートの変更等実施する必要があります。 == チェックアウト除外ファイルの追加 == * 除外したいファイル・ディレクトリを `!` 付きで `/var/www/html/.git/info/sparse-checkout` ファイルに追加します。 * その後下記コマンドを実行すると、指定したファイルないしはディレクトリが削除されます。 * 削除されない場合は `sparse-checkout` ファイルの書き損じの可能性が高いので、注意深く確認します。 {{{#!highlight shell numbers=disable git read-tree -m -u HEAD }}} = よくある質問とその答え = == Q.ProxyPass設定したならProxyReverseの設定は必須では? == A.よくセットで設定されるので必須扱いなのはわかります。が、webhookには高度な応答(リダイレクトやらクッキーやら)機能が無いので必要ないです。 == Q.ウェブフックへのIPアクセス制限を課したいのだが? == A.`https://api.github.com/meta` の応答(JSON)見ると接続元IPアドレスがリストアップしてますねー(棒)。 詳細は[[https://developer.github.com/v3/meta/|REST API v3 - Meta]] を参考に設定してみてください。時々変更してるようなので頑張ってメンテナンスしてください。 == Q.trigger-ruleはどの程度書けば良くて? == A.例えば、マスターブランチへプッシュ以外は無視という設定は可能です。その場合、マスター以外のブランチへのプッシュはエラー応答するため、!GitHub的にはどうかと(障害と区別がつかない)。 !GitHubのWebhooks設定では、「PUSHイベントのみ」、「全てのイベント」、「個別に指定したイベント」と設定できるので、それを受けられるだけの設定を行うようにしましょう。 == Q.ライトなWebサイト運用してる割には凝った設定では? == A.二度と編集しないようなページを作ってるなら必要ない作業ですね。年に一回くらいしか更新しならペイしないかも。 やれ誤字だ脱字だ、一行追加したい、すぐにやれとかワガママに対応したいならここまでこだわらなくていいでしょう。自分にはとてもても。 == Q.CMS使いたくない割には、GitHub使うって無いのでは? == A.Webコンソールでの作業は抵抗無いのですが、HTMLを直接書きたい向けですね。 要はWikiのような軽量マークアップ言語は使いたくない(制御できない、表現力が低い)、覚えたくない(アレするのにコレ、が思い出せない)人向けというのが。 システムサイドとしては、オマケでイシュー管理(めんどくさい修正の丸投げ)やバージョン管理(勝手に変更の追跡)してくれるお手軽さが重要。 知名度も高いので提案しやすいというのもメリット。 == Q.他のサイトでのやり方見るに、胡乱な手順に見えるけどどうして? == A.おっしゃる通り、あまりに胡乱なので今回、フロー図作りました。あまり合理的で無いことは認めます。 ひとえにスナップクラフトによるコンテナ効果(?)が大きく、webhookが動く環境の外に対してアクション起こせないことが原因です。 唯一ホームディレクトリ(`root`で動いてるので `/root` ディレクトリ)のみ、下界(コンテナの外)につながってるので、 そこを通してアクション起こしています。 またアクションを即応させるために、incronを使用して最小限の遅延でコンテンツの同期を行えるようにしています。 普通にcron回してると「更新まだ~?」というF5攻撃が…。 == Q.なんでスナップクラフト使ったの? == A.その辺のメジャーなYUMリポジトリで取り扱ってなかったから。またwehookの開発者公認のリポジトリがスナップクラフトだったので使用。 ソースからコンパイル?もっと楽に生きようぜ。 == Q.よくよく見ると/etcはホストのディレクトリが見えてない? == A.見えてますね。設定ファイル(`/etc/httpd/conf/hooks.yaml`)はコンテナ内でなく、コンテナ外のファイル参照してます。 参照可能なコンテナ外ディレクトリは `/etc`、`/usr/share/`、`/var/lib` のようです。 == Q.となるとホストから参照できるディレクトリ以下にスクリプト置けば遠回りしなくていいのでは? == A.良い点に気がつきました。だが断る。じゃなくて、コンテナ内にgitコマンドが無いので、そこの部分でどうしようもないです。 あと展開したいディレクトリ(通常 `/var/www` ディレクトリ以下)をどうするか…。 もちろんループバックで逃げる手も無くも無いですが、そこまで頑張るならこのやり方の方が…。 = 参考文献 = * webhook関連 * [[https://github.com/adnanh/webhook|webhook is a lightweight incoming webhook server to run shell commands.]] * [[https://snapcraft.io/webhook|Install webhook for Linux using the Snap Store]] * [[https://snapcraft.io/install/webhook/centos|How to install webhook on CentOS]] * [[https://blog.pyyoshi.com/2017/10/26/githubnadonowebhookwozi-qing-nifei-basubian-li-nasaba/|githubなどのwebhookをうけとる便利なGo製サーバー adnanh/webhook]] * [[https://willbrowning.me/setting-up-automatic-deployment-and-builds-using-webhooks/|Setting up Automatic Deployment and Builds Using Webhooks]] * !GitHub関連 * [[https://help.github.com/ja/articles/about-webhooks|webhookについて]] * [[https://developer.github.com/webhooks/|webhooks - GitHub Developer Guide]] * [[https://help.github.com/ja/articles/events-that-trigger-workflows|ワークフローをトリガーするイベント]] * スナップクラフト関連 * [[https://snapcraft.io/docs|Snap documentation]] * [[https://snapcraft.io/docs/snap-layouts|Snap layouts]] * [[https://github.com/adnanh/webhook-contrib/blob/master/snapcraft.yaml|building snaps]] * systemd関連 * [[https://qiita.com/hana_shin/items/b1a269dbee6d96cb3ace|ユニット定義ファイルの使い方]] * [[https://qiita.com/nvsofts/items/529e422bb8a326401c39|systemdで既存のunitを編集する2つの方法]] * [[https://qiita.com/ch7821/items/369090459769c603bb6b|systemd サービスユニット覚書]] * incron関連 * [[https://qiita.com/k-suzuki/items/4a94ebeda9ec75fdad40|incronを使ってみた]] * [[https://lab.tricorn.co.jp/kicco/483|ファイルやディレクトリのイベントに応じてジョブを実行する]]