windyakinってなんて読む

うぃんぢゃきんではない

k3s を運用している

個人的なプロジェクトを外部公開するときや、私的なツール郡を動かすためにVPSを借りている。基本的に自分が作ったサービスたちはコンテナ化されているため、それらのコンテナの起動管理についてはこれまで docker-compose を使っていた。しかし業務で Kubernetes を使う機会が多くなり、コンテナや Kubernetes の運用に関するいろいろな知見も溜まってきたので、自前で Kubernetesクラスタが欲しくなったのだが、趣味にしては維持費が高い。正直そこまで可用性が必要もないが、 ArgoCD で GitOps による自動デプロイとかそういう楽なことだけはしたいという欲が湧いてきたので、 k3s をつかってシングルノードの Kubernetes を運用することにした。この記事ではその際どういったことをしたかなどをまとめる。

k3s のインストール

おもむろに k3s をいれるが、ワン・コマンドで入れられるので便利。なんならアップグレードもこれでできるのですごい。

curl -sfL https://get.k3s.io | sh -

ただしこのままだと kubectl を叩く度に sudo が必要になるので面倒。もう少しよしなにしたい。 Linuxbrew で入れた kubectl で使えるようにするために、 ~/.kube/config に接続設定を含めて出せばいちいち sudo しなくてもよくなる(セキュリティはパーミッションなど最低限のことはやったほうがよい)。

sudo kubectl config view --raw > ~/.kube/config

SealedSecret

Git で Kubernetes の Manifests を管理するとき、 Secret をそのままコミットすると危険なのだが、 SealedSecret を使うことで暗号化した状態にできる。暗号化を行うために一度クラスタに Secret を読み込ませる必要があったり、情報の更新が若干面倒というデメリットもあるが、そのあたりは GitOps したい度とのトレードオフだろう。

kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/${VERSION}/controller.yaml

${VERSION} は SealedSecret のリポジトリの最新を使うとよい。

Secret 情報の暗号化は kubeseal コマンドを使って行う。Secret の manifests が用意できていれば以下のようなコマンドを実行する。

kubeseal --scope namespace-wide -o yaml < secret.yaml > sealedsecret.yaml

ArgoCD

GitOps のために ArgoCD を入れる。 core-install.yaml はコア機能のみで Web UI などはインストールされない。

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/core-install.yaml

初めて触れるなら Web UI はあったほうがよさそうに思う。リソースの管理がどういうものかとか、更新のタイミングなども見ることができるので面白い。

ArgoCD の使い方は公式のドキュメントが一番くわしい。

CLI で操作する場合は argocd コマンドなどを導入すると GitOps につかうリポジトリの登録や、実際のアプリケーションの設定などがコマンドからできて便利。

argocd repo add git@github.com:windyakin/k8s-manifests.git --ssh-private-key-path ${SSH_PRIVATE_KEY_PATH}
argocd app create argocd-applications \
  --repo git@github.com:windyakin/k8s-manifests.git \
  --path argocd-application \
  --auto-prune \
  --dest-namespace default \
  --dest-server https://kubernetes.default.svc \
  --revision HEAD

k3s 運用してみてどうか

もともとは microk8s を導入していたのだが、どういうわけか liveness probe を入れたあたりから KubernetesAPI が重くなってしまい、 kubectl すらまともに使えなくなってしまったため、 k3s に乗り換えたという経緯があった。 コンテナを立てて運用する程度であれば k3s の機能でも十分であり、 microk8s に比べるとオーバーヘッドも少なく助かっているところではある。

またもともとやりたかった GitOps による運用ができたのも楽でよい。基本的にほったらかしで、監視等も入れていないのだが、不自然な挙動などはなく正常にうごいている。ただ貧弱なサーバーのシングルノードなので可用性の向上や、スケーリングといった恩恵は受けることは出来ない。 Kubernetes の美味しいところを知っていてそれにあやかりたい場合は、趣味の割り切りとして使ってよいのではないだろうか。

Fluentd で使いたい Gem の依存関係の解決は Bundler に任せられる

Fluentd は Gem によるライブラリの追加ができて、例えばログを直接 BigQuery に転送したいのであれば fluent-plugin-bigquery のような Gem をインストールしておくことによってそれを実現することができる。

こういったプラグインを追加するコマンドとして提供されているのが fluent-gem というコマンドで、例えば先程挙げた Gem をインストールしたいのであれば

fluent-gem install fluent-plugin-bigquery

のようにコマンドを叩けば Gem をインストールしてくれる。こういった Fluentd 向けのプラグインrubygems.org にて様々提供されているので、使っているうちにあれもこれもと追加したくなるのだが、徐々にパッケージの依存関係がややこしくなっていき、最終的に人間による依存関係の解決はできなくなってしまう。

Fluentd でも Bundler が使える

Fluentd で使う Gem をインストールするためには fluent-gem という特別なコマンドを使うので、コマンドの裏ではさぞ特殊なことをやっているのだろうと思ったのだが、ソースコードを見るとそんなことはなく、ただ単純に Gem をインストールしているだけのようだった。

また Fluentd の起動オプションには Gemfile を読み込ませる --gemfile オプションがあり、これで指定した Gemfile を使って起動時に Bundler が Gem をインストールしてプラグインが使えるようになる。

docs.fluentd.org

つまり Fluentd においても Gem の依存関係の解決は Bundler に一任することができるようになっている。

Dockerfile にしてみる

例えば fluent-plugin-bigqueryfluent-plugin-google-cloud を同時に使いたい場合、このような Gemfile を書くことになる。

source "https://rubygems.org"

gem 'fluentd', '~> 1.13.x'
gem 'fluent-plugin-bigquery', '~> 2.2.x'
gem 'fluent-plugin-google-cloud', '~> 0.12.x'

Dockerfile ではビルド時に bundle install しておくことをおすすめする。 Fluentd の起動時にも bundle install は実行されるが、 Gem のインストール時に必要なネイティブライブラリなどは予め用意しておく必要があるからである。また Fluentd の公式イメージである fluent/fluentd をベースイメージとする際は、実行ユーザーが fluent になっているため、 apt-get install などを実行するのであれば予め root を指定する必要がある。ただし bundle install の実行ユーザーは fluent になっているほうがファイルの権限管理などに面倒がない。

FROM fluent/fluentd:v1.13-debian

COPY Gemfile Gemfile.lock /fluentd/etc/

USER root

RUN apt-get update \
  && apt-get install -y --no-install-recommends \
    build-essential \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

USER fluent

RUN cd /fluentd/etc \
  && bundle install -j8 --path vendor/bundle

CMD [ "fluentd", "--gemfile", "/fluentd/etc/Gemfile" ]

また、このとき Gemfile.lock をどう用意するかも考える必要があるが、一度 Gemfile.lock なしでビルドしたイメージから docker cp などを使って /fluentd/etc/ 以下にある Gemfile.lock を取り出すと一応用意することができる。

% docker build -t fluentd .
% docker run --rm -d --name fluentd fluentd tail -f /dev/null
% docker cp fluentd:/fluentd/etc/Gemfile.lock .
% docker kill fluentd

ここまでの流れをサンプルリポジトリにまとめたので参考までに。

Nuxt2でコンポーネントから今表示しているページのタイトル要素を取りたいとき

Nuxt2 でコンポーネントから今表示しているページのタイトル要素を取りたいケースがある。

具体的に言うと Twitter のツイートボタンを自前で実装するときに使えるリンク形式でツイートを発行できる機能があり、このツイートするテキスト部分ににページのタイトルを含めたかった。

https://twitter.com/intent/tweet?url=[ツイートしたいURL]&text=[タイトルを含むテキスト]

このとき、 Nuxt のコンポーネント側からタイトルを知る方法として一番最初に思いつくのは document.title という JavaScript の古典的な API である。

export default {
  data() {
    return { pageTitle: '' }
  },
  mounted() {
    this.pageTitle = document.title
  }
}

これは確かに最初に表示したページやコンポーネントがロードされたタイミングのページのタイトルを取得することはできるが、 Nuxt 内に <nuxt-link> のを含んでいて、遷移の際にコンポーネントがそのまま表示され続けるような場合は値の変更がされない。考えてみれば「それはそう」だし。単純に自分の想定があまい。

じゃあページ変更をトリガーすれば良いんだろうということで、 $routewatch によって監視することでページ遷移のタイミングで document.title を取得し直せばよいのだろうということでこういうコードを書いた。

export default {
  data() {
    return { pageTitle: '' }
  },
  mounted() {
    this.pageTitle = document.title
  },
  watch: {
    $route(newValue) {
      this.pageTitle = document.title
    }
  }
}

しかしこれもうまく行かなかった。たしかに watch.$route はページ遷移のときに通っていくのだが、このタイミングで document.title を叩いても返ってくるのは遷移前のページのタイトルなのである。つまり document.title が変更される前にここを通っているため、ページ遷移をすると1ページ前に見ていたタイトルしか取得できないように見える。

最終的に StackOverflow でこういう記事を見つけた。

記事内のコードに書いてあるとおりで、HTML DOM を監視する JavaScript API である MutationObserver を使って <title> タグを監視して更新するといったもの。

書いてみるとまあ確かに動く(単純に Mutation Observer を使っているだけなので)。

export default {
  data() {
    return { pageTitle: '' }
  },
  mounted() {
    this.pageTitle = document.title
    new MutationObserver(() => {
      this.pageTitle = document.title
    }).observe(document.querySelector('title'), { childList: true })
  }
}

動いたのはいいが、なんだかもやもやするコードである。本当は上位から表示しているページのタイトルの内容を通知するといいのだろうが、 layout に埋め込まれているコンポーネントの場合、そのデータのやりとりが若干ややこしくなってしまう。正直ページタイトルを取得するためにそこまでやりたくはないし、こういうユースケースはよくありそうなので、できれば Nuxt 側で取れるようになってほしいなと思った。

GitHub Actions をつかって Google Cloud Functions にデプロイする

f:id:windyakin:20210319232925p:plain

Google Cloud Functions ってやつ、 Slack の Webhook 系のアクションだとか、情報を整形して別の API横流しするだけの Proxy 系アプリケーションなんかのホスト先として便利に使ってたのだけども、その簡単さからCDの構築をサボっていて、よくリポジトリの状態と実際に展開されているコードが違うみたいな状態のまま放置してしまうなどどうにもうまく管理ができていなかったんですよね。まあ先述の通りそんなに重要なアプリケーションを管理しているわけでもないので、長らく自分の中での「動いてるからいいや」の代表的な存在だったのですが、やっぱりCVEなんかで報告されているような脆弱性を抱えるライブラリをそのまま放置するのはよくないなという気持ちになり、重い腰を上げてCD環境を整備することにしたのでそのメモ。

続きを読む

curlコマンドでHTTPステータスコードだけを取得する場合は--write-outオプションを使うと良い

シェルスクリプトを書いているとcurlコマンドなんかで指定したURLのHTTPステータスコードだけがほしいという場合がある。

結論から言うと --write-out / -w というオプションを使うと実現できる

% curl -s -o /dev/null --write-out "%{http_code}" https://www.example.com/
200

-o /dev/null にしているのは取得内容を標準出力に表示しないようにするもので、こうすることで文字列として出力されるのはHTTPステータスコードだけになりパイプやシェルスクリプトで処理がしやすいなどのメリットがある。

--write-out で出力できる内容いろいろ

ところで --write-out とはなにか。こういうとき、脳死Google検索すると上位にQiitaというウェブサイトがでてきてしまうのだが、原典をみるのが一番確実である。

curl が指定URLにアクセスしたときに取得する様々な情報を簡易的なテンプレート表現で出力させることができるコマンドで、HTTPステータスコード%{http_code} 以外にも、アクセスのために必要な処理にかかった各種の時間なども出力させることができる。

curl のバージョンによっては使えないパラメータも多々あるので、手元のバージョンで使えるものを確認するには curl --manual--write-out の項を確認するとよい。