Pull RequestのコメントからGitHub Actionsを実行する

GitHubのPull Requestのコメントから任意のGitHub Actionsを実行する必要があり、やり方に少々癖があったので紹介します。

issue_comment

ドキュメントを眺めているとPull Requestに関連するイベントはいくつかありますが、Pull Requestのコメントをトリガーとしたイベントが無いように見えます。

どうにかPull Requestのコメントをトリガーに起動できないか調べていたらCommunity Forumで以下の投稿を見つけました。

github.community

この投稿によるとissue_commentイベントでPull Requestのコメントもトリガーとなるようです。
Issueへのコメントもトリガーの対象となるので、適切にフィルタリングする必要があると書かれています。

実装

今回は/planとコメントに入力することでterraform planが実行されるGitHub Actionsを作ってみたいと思います。

stepsまでのコードが以下になります。

name: "/plan"
on:
  issue_comment:
    types: [created, edited]

jobs:
  manual_plan:
    name: "manual plan"
    if: contains(github.event.comment.html_url, '/pull/') && startsWith(github.event.comment.body, '/plan')
    runs-on: [ubuntu-latest]
    steps:

ポイントはジョブ全体でifを設定して起動する条件を設定しているところです。

contains()でPull Requestのコメントのみに反応するようにし、startsWith()/planとコメントに入力された場合のみにしています。

実行されるブランチ

上記の設定のままstepsで処理を書いていけば意図した通りに動作しそうですが、1つ問題があります。
issue_commentをトリガーとした場合、actions/checkoutでpullできるブランチがmasterブランチになってしまいます。(正確にはリポジトリのデフォルトブランチ)

Pull Requestのコメントで実行するので、そのPull Requestのブランチでジョブを実行したいです。

actions/checkout@v2からはブランチを指定してpullできるようになりました。
Actionsをトリガーした内容が入った変数、つまり${{ github }}をもとにGitHubAPIを使ってPull Requestのブランチ名を取得します。
それをactions/checkout@v2にセットし、Pull Requestのブランチでジョブを実行できるようにします。

    steps:
      - name: get upstream branch
        id: upstreambranch
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')"

      - name: Checkout upstream repo
        uses: actions/checkout@v2
        with:
          ref: ${{ steps.upstreambranch.outputs.branchname }}

Pull Requestに結果を書き込み

せっかくなのでplanの結果をPull Requestのコメントに書き込みたいと思います。

GitHubに関連する操作はactions/github-scriptを使えば大体の事ができます。

github.com

      - id: plan
        run: terraform plan -no-color

      - name: plan success message
        uses: actions/github-script@0.9.0
        if: steps.plan.outputs.exitcode == 0
        env:
          STDOUT: "```${{ steps.plan.outputs.stdout }}```"
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: "#### `terraform plan` Success\n" + process.env.STDOUT
            })

LFSをやってみたので振り返る

長期休暇を利用してLFSをやってみたので振り返ってみたいと思います。

きっかけ

日頃から業務でLinuxを使ってサービスを運用していますが、最近はクラウドやコンテナなどを使うことがほとんどでLinuxのほんの一部だけを知っていれば運用できています。
しかし、何らかの障害が発生したときの調査は平常時に使う一部の知識だけでは難しいことが多いです。
そんな状態から脱出すべく、以下の書籍を読みました。

gihyo.jp

www.sbcr.jp

これらを読んでいるとLinuxの仕組みがわかってきますが、ただテキストを読んでいるだけだとどうしても印象が薄くなってしまい忘れてしまうことが目立ってきました。
そこで何か手を動かしてできることがないか調べてみるとLFSLinux From Scratch)を見つけたので、やってみることにしました。

期待したこと

LFSをやることで以下の内容を取得できることを期待して手を動かしてみました。

  • Linuxの基盤となる部分を知る
  • サービスを運用するときの障害対応力を上げる

構築中に感じたこと

Linuxで使われるファイルやディレクトリを手作業で作成する

LinuxをインストールするとFHSに即したディレクトリが構築され、/etc/passwd/etc/groupには初期のユーザー情報が記述されています。
LFSで1から構築する場合はこれらのファイルやディレクトリも自分で作成することになります。
当たり前だと言えばそうかもしれませんが、なかなか新鮮な感覚でした。

ソフトウェアのバージョンを合わせることの重要性

LFSは構築に使うパッケージを全て自前でビルドします。
その関係上、まずはホストシステムでLFSの構築に使う環境の構築を行います。この環境の構築はホストにインストールされているソフトウェアを使うため、このバージョンが異なっていると予期せぬエラーに遭遇することもあります。

Linux KernelやGlibcなど、変更するのが大変なものもありますが、ホストのOSを変えるなどして、できる限り合わせる必要があります。
最低でもマイナーバージョンまでは揃えるべきです。

ホスト環境をチェックするスクリプトが載っているので、よくチェックすることをオススメします。

lfsbookja.osdn.jp

クラウドでは使用頻度が少ないコマンドを実行する

LFSを始めると最初に専用のパーティションを作成することになります。
パーティションを作成するためにfdiskコマンドを実行したり、それをマウントするためのmountコマンド、/ets/fstabへの記述、そして構築が進んでいくと作り途中のLFS環境に入るためのchrootコマンド、デバイスノードを作成するmknodコマンドなど、クラウドでサービスを運用しているとあまり使わないコマンドを実行します。

クラウド以外で運用する場合やLinuxに関連する開発を行う場合、これらのコマンドは定期的に使うことになるので久しぶりに実行して思い出すことができたのは良い機会だったと思います。

知らないこと、忘れていることはその場で調べる

これが今回の構築で一番良かったところです。

LFSのドキュメントはとてもよくできており、ほぼ全ての作業をコピペするだけで構築できます。
そのコピペするコマンドの中には上記の本で触れられているがあまり使わないコマンドや、そのそも出てこないものなどもあります。
それらのコマンドを知らないままでもコピペで実行すれば構築できてしまいますが、コマンドのオプションも含め、知らないものは全て調べます。
時間はかかりますが勉強になります。

また、LFSでは全てのパッケージをビルドするため、インストールする全てのライブラリに触れます。
その中には知らないパッケージや生成されるコマンドがあると思います。
LFSのドキュメントに簡単には説明が書いてありますが、これも調べるととても勉強になります。

期待したものは得られたか

構築が完了した時点で期待した成果が得られたのか振り返ります。

Linuxの基盤となる部分を知る

これに関しては十分に達成できたと思います。
ただ、ライブラリやコマンドはビルドしただけなので、ふつうのLinuxプログラミングで書かれている内容をもう一度やってみるとさらなる理解が得られそうな気がしています。

サービスを運用するときの障害対応力を上げる

障害対応力は構築が完了した段階だとまだ上がった実感はありません。
少なからず関連はしていると思うので、実際に対応を行ってみて変化を感じるかどうか様子を見てみたいと思います。

今後にむけて

サービスをLinuxの上で運用していくことはまだしばらく続くと思うので、さらなる理解と障害対応力を磨いて行きたいと思います。
そのために次はこれらの書籍をよんでみようかと思っています。

www.seshop.com

www.oreilly.co.jp

TerraformのStateをS3からTerraform Cloudに移行する

TerraformのStateをチームで管理するときに、複数の環境からplanやapplyを実行できるようにするためにS3などのクラウドのストレージをバックエンドにすることが多いと思います。

通常のストレージをバックエンドにしていると、複数の環境から同時にapplyが実行されたときに壊れてしまう可能性があります。CIで実行するようにしていても、設定が適切ではなく平行に動いてしまうような場合などに壊れる可能性があります。

Terraform CloudにはStateのロック機能がありこのような事故から開放されるため、Stateの管理をS3からTerraform Cloudに移行したいと思います。

環境

  • Terraform v0.12.24
  • 元のBackendはS3
  • Terraform Cloudのアカウントは作成済み

手順

Terraform Cloudへのアクセス設定

まずはterraformコマンドがTerraform Cloudにアクセスできるようにします。

terraformのバージョンが0.12.21以降の場合は terraform login コマンドを実行して、ブラウザに表示されたトークンを入力すれば完了です。

www.terraform.io

loginコマンドが使えない場合は $HOME.terraformrc というファイルを作成し、以下のようにトークンを設定します。

credentials "app.terraform.io" {
  token = "xxxxxx.atlasv1.zzzzzzzzzzzzz"
}

また、TF_CLI_CONFIG_FILE という環境変数を設定すれば、任意の場所に任意の名前で設定ファイルを作成できます。

www.terraform.io

www.terraform.io

backendの書き換え

S3の設定が書かれていたbackendをTerraform Cloudのものに書き換えます。

  backend "s3" {
    ...
  }

これを削除して以下のように書き換えます。

  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "your_organization_name"

    workspaces {
      name = "your_workspaces_name"
    }
  }

移行

準備が整ったので移行させていきます。

terraform init を実行するだけで移行ができます。
実行すると以下のように聞かれるので yes を入力します。

$ terraform init

Initializing the backend...
Backend configuration changed!

Terraform has detected that the configuration specified for the backend
has changed. Terraform will now check for existing state in the backends.


Terraform detected that the backend type changed from "s3" to "remote".
Acquiring state lock. This may take a few moments...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "s3" backend to the
  newly configured "remote" backend. No existing state was found in the newly
  configured "remote" backend. Do you want to copy this state to the new "remote"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value:

yes を入力し、問題なく成功すれば以下のように表示されます。

Successfully configured the backend "remote"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

これで移行が完了です。

参考

Kubernetes上で動くGoサーバーでプロファイラを動かしWeb UIで表示する

Goにはプロファイラとして標準パッケージにpprofが搭載されています。
pprofの使い方としてはすでに多数の優良記事が存在するため、ここでは扱いません。

今回はpprofをk8s上で動くサーバーに対して実行し、結果をWebUIで表示する必要があったのでそのやり方を紹介します。

環境

インフラ構成

  • GoサーバーのHTTP API用のPortが80
  • pprof用のPortが6060
  • プロファイラ結果のWeb UI用のPortが8080
    • このPortをServiceのNodePortでMacに公開

やり方

pprofの設定

まずは通常通りpprofの設定をしてGoをビルドします。

import (
    ...
    _ "net/http/pprof"
)

func main() {
    ...

    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    ...
}

このバイナリがk8sのPodで動いているとします。

Serviceの定義

通常のAPIへのアクセスに使うServiceが以下です。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-service
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80

これに追加してpprofのWebページにアクセスするためのServiceを定義します。
このServiceのTypeをNodePortにすることでお手軽にアクセスできるようになります。

apiVersion: v1
kind: Service
metadata:
  name: my-service-pprof
spec:
  selector:
    app: my-service-api
  type: NodePort
  ports:
    - name: http-pprof
      protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 30080

pprofの実行

Goのバイナリが動くPodに入り以下のコマンドでpprofを実行します。

$ pprof -http=0.0.0.0:8080 /usr/local/bin/my-api http://localhost:6060/debug/pprof/profile

ここで大事なのはpprofのWebページのアドレスを 0.0.0.0 にすることです。ここを 127.0.0.1 にしていると外部からアクセスできなくなってしまいます。

あとは計測したいエンドポイントにアクセスし以下のようなメッセージが表示された後、ホストからNodePortの 30080 にアクセスすればプロファイル結果がWebUIで表示されます。

Saved profile in /root/pprof/pprof.my-api.samples.cpu.001.pb.gz
Serving web UI on http://0.0.0.0:8080
Couldn't find a suitable web browser!
Set the BROWSER environment variable to your desired browser.

Magiskのroot化を維持したままOTAアップデートする

Magiskでroot化するとOTAアップデートが無効化されるため、セキュリティパッチなどのOSのアップデートを行うことができなくなります。
しかし、適切な手順を踏むことでroot化を維持したままOTAアップデートを行うことができます。

検証環境

  • Pixel3
  • Android 10
  • TWRPを使用しないroot化(boot.imgのパッチ)

手順

注意事項

以下の手順の中で再起動を促すボタンが数回表示されますが、再起動してはいけない場面が多数あります。
手順の中で再起動をすると記載したとき以外は再起動のボタンが表示されてもタップしないように注意してください。
root化を維持できなくなります。

大まかな流れ

  • 一時的にMagiskをアンインストール
  • アップデートをインストール
  • アップデート後のイメージにMagiskをインストールするように設定する

Magiskのアンインストール

まずはOTAアップデートをインストールするために一時的にMagiskをアンインストールします。

Magisk Managerを起動し、アンインストールをタップします。
その後に表示されるダイアログでイメージの復元を選択します。
復元が完了しました!と表示されればOKです。

ここで再起動をしてはいけません。

アップデートのインストール

1つ前の作業で正規のイメージになったため、アップデートのインストールができるようになりました。正規の手順でOTAアップデートを行います。

バイスの[設定]→[システム]→[システムの更新]に移動し、 [ダウンロードとインストール]ボタンを押してインストールします。

インストールが完了すると再起動のボタンが表示されますが、タップしてはいけません。

Magiskの再インストール

再度Magisk Managerを開き、インストールをタップします。
タップ後に表示されるダイアログで非アクティブスロットにインストール(OTA後)を選択します。
警告が表示されますが、はいを選択してインストールを行います。
インストールが完了すると、再起動のボタンが表示されるのでタップして再起動します。

これでOTAアップデートにMagiskがインストールされます。

参考

Magiskでroot化したPixel3のテザリング制限を解除する

民泊系と呼ばれるSIMを使っている場合、制限がかかっていてテザリングをすることができません。

しかしMagiskでroot化していればこの制限を解除でき、民泊系の数百GB/月という容量を使ってテザリングをすることができます。

前提

必要なモジュールのインストール

テザリングの制限を解除するためにMagiskHide Props Configを使います。
このツールはbusyboxというツールに依存しているためこちらもインストールします。

インストールはMagisk Managerのタブにあるダウンロードを表示し、そこにあるモジュールの一覧から見つけ出して画面の案内にそってインストールします。

テザリング制限を解除

適当なターミナルアプリを用意して以下のコマンドを順番に入力していけば解除できます。
ターミナルアプリがインストールされていない場合は以下のアプリを入れましょう。

play.google.com

ターミナルから以下のコマンドを順に入力します。

  • su
  • props
  • 4
  • n
  • net.tethering.noprovisioning
  • true
  • y

yを入力後に端末が再起動します。

再起動後、設定からテザリングを有効にできます。

MacのCLI環境の構築を自動化する

前回、IDEをやめてvim-lspと独自コマンドに移行したことを紹介しましたが、あれだけだと少し問題があります。

akaimo.hatenablog.jp

PCを買い替えたときやクリーンインストールをしたときなどに同じ環境を簡単に構築することができないともう一度構築することが面倒になってしまい、インストールするだけで使えるIDEでいいやとなってしまいます。

そこで今回はAnsibleを使ってCLI環境の構築を自動化したときに簡単に使えるように意識したポイントを紹介します。
Ansible自体の使い方は紹介しませんので別の記事を参照してください。

自動化したコードは以下のリポジトリで公開しています。

github.com

ポイント

以下のことを意識して自動化しました。

  • 詳細な手順が書かれたドキュメントを用意する
  • できるだけ実行するコマンドを少なくする
  • アップデートしやすくする
  • 別のOSでも動くようにする

1つずつ紹介していきます。

詳細な手順が書かれたドキュメントを用意する

いくら自動化していても、どのように実行すればいいか忘れてしまうと構築することができません。自動化した直後は覚えていても時間が経てばほぼ確実に忘れます。そのため自動化のキック方法をREADMEなどに書いておくべきです。

また、自動化に使っている依存ツール(今回であればAnsible)があればそれのインストール方法も書いておくと良いでしょう。その他にも前提条件があれば書いておきます。

READMEだけで構築が完了する粒度を意識します。

できるだけ実行するコマンドを少なくする

完全な自動化は難しいところもありますが、できる限り自動化して少ないコマンドで構築が終わるようにします。

このときにインストール対象に合わせていくつかのコマンドに分けることをオススメします。
これをやっておくことで構築するときに、そのとき必要なものだけインストールすることができます。
さらに、OSのアップデートなどで自動化が壊れたときにデバッグと修復が行いやすくなります。

アップデートしやすくする

構築後に新たな拡張をした場合、次に構築するときに追加した部分も反映されて欲しいです。そのためには自動化した部分を適宜アップデートする必要があります。
アップデートするときに手間がかかるような状態だと面倒になり放置してしまい、最終的にはアップデートする必要があることを忘れます。
これはもったいないのでアップデートしやすくしておきます。

具体的にやることとしては、

  • with_itemsで重複の排除と項目の列挙
  • 複数のタスクで読み込まれる項目は
    • include_varsで別ファイルから読み込む
    • registerで変数にする

を行いました。
これを行うと定義が見やすくなるだけでなく、実行ログに項目が表示されるようになるためエラーとなった項目を素早く見つけることができます。

また、各タスクにきちんとnameを定義しておくこともメンテナンス性の向上につながります。

別のOSでも動くようにする

インストールコマンドで各OSのパッケージマネージャを使うなど、OSごとに処理が分かれる部分がでてくると思います。
1つの環境でしか動かさないのであれば問題ありませんが、CLI環境はOSによる差を吸収しやすい部分ですし、サーバーにインストールする可能性も考慮するとどのOSでも構築できるようにしておくと便利だと思います。

OSによる分岐を行うには、タスクにwhenを追加しOSの判定をすれば良いです。
Macの場合はansible_os_family == 'Darwin'と書けば判定できます。

OSによる違いがたくさんある場合はOSごとにファイルを用意してincludeに対してwhenを使ってまとめて切り替えたりもできます。

おわりに

今回はCLI環境の構築を自動化するテクニックを紹介しました。
CLI環境は自分に一番合った設定にカスタマイズできる反面、インストーラーやインポート機能のようなものはなく、再度構築するのに時間がかかったり最悪の場合は再現できなくなってしまうリスクもあります。このようなことにならないためにも自動化をしておくことがオススメです。
自動化しておくことでサーバーで作業するときにも気軽に同じ環境を構築できパフォーマンスもあがります。