mysql クライアントのみをインストールした状態で mysql2 gem をビルドする
この記事の続きです。
よく考えると mysql サーバをローカルにインストールする必要はないので、以下のようにやってみました。
brew uninstall mysql brew install mysql-client # mysql との競合を避けるため /usr/local/opt/mysql-client 以下に入るので、以下のようにオプションを調整 bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/mysql-client/lib --with-cppflags=-I/usr/local/opt/mysql-client/include"
これで入りました。
Firebase Authentication の ID トークンを Ruby で検証する
ID トークンを確認する | Firebase にあるように、Fireabse Authentication によって発行された ID トークンを正しく検証することにより、そのユーザの user_id を確認することができます。
Firebase Admin SDK が提供されていればそれを使うことで簡単に検証できるのですが、Ruby 版は提供されていないので Rails から使いたい場合などは自分で検証処理を書くことになります。
検証すべき内容は ID トークンを確認する | Firebase に書いてあるのでそれに沿って書いていきます。
要: JWT gem
# @see https://firebase.google.com/docs/auth/admin/verify-id-tokens?hl=ja # # Usage: # validator = FirebaseAuth::TokenValidator.new(token) # payload = validator.validate! # class FirebaseAuth::TokenValidator class InvalidTokenError < StandardError; end ALG = 'RS256' CERTS_URI = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com' CERTS_CACHE_KEY = 'firebase_auth_certificates' PROJECT_ID = 'YOUR_PROJECT_ID' ISSUER_URI_BASE = 'https://securetoken.google.com/' def initialize(token) @token = token end # # Validates firebase authentication token # # @raise [InvalidTokenError] validation error # @return [Hash] valid payload # def validate! options = { algorithm: ALG, iss: ISSUER_URI_BASE + PROJECT_ID, verify_iss: true, aud: PROJECT_ID, verify_aud: true, verify_iat: true, } payload, _ = JWT.decode(@token, nil, true, options) do |header| cert = fetch_certificates[header['kid']] if cert.present? OpenSSL::X509::Certificate.new(cert).public_key else nil end end # JWT.decode でチェックされない項目のチェック raise InvalidTokenError.new('Invalid auth_time') unless Time.zone.at(payload['auth_time']).past? raise InvalidTokenError.new('Invalid sub') if payload['sub'].empty? payload rescue JWT::DecodeError => e Rails.logger.error e.message Rails.logger.error e.backtrace.join("\n") raise InvalidTokenError.new(e.message) end private # 証明書は毎回取得せずにキャッシュする (要: Rails.cache) def fetch_certificates cached = Rails.cache.read(CERTS_CACHE_KEY) return cached if cached.present? res = Net::HTTP.get_response(URI(CERTS_URI)) raise 'Fetch certificates error' unless res.is_a?(Net::HTTPSuccess) body = JSON.parse(res.body) expires_at = Time.zone.parse(res.header['expires']) Rails.cache.write(CERTS_CACHE_KEY, body, expires_in: expires_at - Time.current) body end end
macOS Mojave にアップグレードしたら mysql2 gem のインストール時にエラーが発生した
手元の環境では High Sierra のときはこの辺の考慮をしなくてもインストールできていた気がするのですが、さっき新規に bundle install をしたら以下のようなエラーになってしまいました。
ld: library not found for -lssl clang: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [mysql2.bundle] Error 1
brew でインストールした openssl が見つからないだけのようで、以下のような対応を行いました。(確か Sierra 以前の頃はこれやってた気がするんですが、なぜ High Sierra のときにはやらずにインストールできていたのだろう。。)
bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include"
参考: https://github.com/brianmario/mysql2/issues/1005
ちなみに今開発しているアプリのデプロイ先が App Engine Flexible Environment (ruby) なので、ruby や node.js をローカルにインストールしちゃう方法で開発を行なっています。 もちろんバージョンは揃えたい(開発者間での差異を無くしつつ、プロジェクトごとに違うバージョンを使えるようにしたい)ので、ruby や node は rbenv / nodenv を使い、MySQL 等のミドルウェアは dokcer-compose を使うようにしていて、コンテナとの付き合い方はこのぐらいの力の入れ具合がちょうどバランスいいかなと個人的には感じています。
とはいえローカルに開発環境を構築している関係上、今回のような個人の環境に依存する問題が発生してしまうことがありますし、実行環境が k8s だったり App Engine の custom runtime だったら全部コンテナ上に乗せて開発する方法を検討すると思いますのでその辺はケースバイケースですね。。
Angular で作った SPA を Docker の nginx を使って動かす
このサイトにある手順をまるっと使わせていただきました。
追加したのは以下の3ファイル
.dockerignore
node_modules
nginx.conf
server { listen 80; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html =404; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
Dockerfile
FROM node as build-stage WORKDIR /app COPY package*.json /app/ RUN npm install COPY ./ /app/ ARG configuration=production RUN npm run build -- --output-path=./dist/out --configuration $configuration FROM nginx COPY ./nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build-stage /app/dist/out/ /usr/share/nginx/html
以下の手順でビルド & 公開
$ docker build -t <IMAGE_TAG> . $ docker run -p 8080:80 <IMAGE_TAG>
これで localhost:8080 でアクセスできる。
一般公開だったら Firebase Hosting とかで良いのですが、今回は社内イントラ内での公開だったのでこのようにしました。
Kubernetes の CronJob で bundle update して PR 投げてくれるようにした
bundle update を定期的に実行し差分がある場合は pull request を投げる、というよくある仕組みを k8s の cronjob で実装しました。
clone ( or pull ) -> 何らかの処理を行う (ここでは bundle update) -> [差分があったら] -> commit & push -> Pull Request を出す、という処理は今回の bundle update に限らず汎用的に使えるものだと思うので、コマンドラインツールとして Go で実装しました。
あとは任意のコンテナの中で上記 prbot のバイナリを取得して実行すれば OK。
以下のような manifest を書きます。 Clone や PullRequest の送信に GitHub の Personal Access Token が必要になるので取得して Secrets として登録しておいてください。
apiVersion: batch/v1beta1 kind: CronJob metadata: name: bundle-update spec: schedule: "0 2 * * WED" # cron式 jobTemplate: spec: template: spec: restartPolicy: Never containers: - image: library/ruby name: ruby command: [ "/bin/sh" ] args: - "-c" - |2 set -e wget https://github.com/satococoa/prbot/releases/download/v${PRBOT_VERSION}/prbot_${PRBOT_VERSION}_linux_amd64.tar.gz tar -xvzf prbot_${PRBOT_VERSION}_linux_amd64.tar.gz mv prbot /bin/prbot chmod +x /bin/prbot gem install bundler prbot env: - name: PRBOT_VERSION value: "0.1.2" - name: GITHUB_REPOSITORY value: "org/repository" - name: GITHUB_ACCESS_TOKEN valueFrom: secretKeyRef: name: bundle-update-secret key: githubAccessToken - name: BASE_BRANCH value: "master" - name: COMMAND value: "bin/bundle update" - name: TITLE value: "pull request のタイトル" - name: AUTHOR_NAME value: "prbot" - name: AUTHOR_EMAIL value: "prbot@example.com"
以上です。 使うイメージについては native extension を含む gem なんかが含まれる場合は library/ruby じゃビルドできないかもしれないので、実際にアプリケーションを動かすコンテナで実行する方が確実だと思います。 library/ruby は buildpack-deps を基に作られていて mysql-client なんかは入ってるため、僕の場合は library/ruby で大丈夫でした。
学び
- GoReleaser が超便利。
- マルチプラットフォーム向けにビルドして バイナリを GitHub の Release にアップロードして、さらに Docker イメージとか Homebrew 用のレシピとかも作れちゃう。
- buildpack-deps という Docker image を知れた。
複数の pod で並列に kubectl exec を実行する
こんな感じで OK
kubectl get pods -l name=app -o jsonpath="{.items[*].metadata.name}" | xargs -n1 -P4 -I{} kubectl exec {} bin/rails r 'Rails.logger.info(Rails.env)'
xargs の P オプションの値は最大プロセス数。
参考: JSONPath Support kubernetes.io
Rails 5.1 で rails test と rails test:system の RAILS_ENV に関する挙動の違いメモ
RAILS_ENV が指定されている場合 rails test
コマンドと rails test:system
コマンドの挙動が違う。
おそらくそれぞれ rails command として実装されているか、rake タスクとして実装されているかによる違い。
rails test
で実行 → RAILS_ENV が test に上書きされて実行されるrails test:system
で実行 → RAILS_ENV に指定された環境で実行されるrails test:db
も同様
docker-compose.yml で RAILS_ENV=development としていたコンテナでシステムテストを実行して気づいた。
今はテスト実行のコマンドを rails test test/*
とすることで回避している。
(ちなみに rails test
がシステムテストを実行しないのは rails の意図した動作である。)
これは rails の意図した動作なのか、それとも意図していない動作なので rails/rails を直すべきなのか、またはコンテナに RAILS_ENV を指定しないべきなのか判断がつかなかったけれども、あとで忘れるといけないのでメモだけ残す。