VSCodeでRustのテストコードをデバッグできない問題

2018エディションにNLLも入って久しいし、tonic(async/await構文でgRPCサービスを書けるライブラリ) からベータが取れたぞということで久しぶりにRustやろうという気持ちが高まってきました(2年ぶり3回目)。

まずは開発環境を更新せねばということで諸々アップデートして動作を確認していたのですが、 相変わらずVSCode + LLDBという構成だとlibクレートの単体テストをデバッグするときにブレークポイントが効かない。binクレートの単体テストだと効く。 なぜか日本語のブログではこの問題に触れているのを見たことがないんですよね (そういう入門記事はだいたいサンプルがbinクレートなので、この問題を踏むのはもっと後のステップではあるんですが)。

私の環境固有の問題かなとも少し思ったんですが、GitHubのIssueを漁ってみると同じ問題を踏んでそうな人をちらほら見かけたので、 今回はちょっと時間を使って原因を調べてみることにしました。

問題の再現手順とかワークアラウンドのスクリプトは下記のリポジトリにも置いてあります。

https://github.com/kwhrtsk/rust-lldb-workaround

解決方法だけ知りたい人はVSCode (CodeLLDB) の場合を参照してください。

問題の概要

macOSにおいて、rust-lldb でlibクレートのテストをデバッグしようとしたときに、ブレークポイントが効きません。 VSCodeで CodeLLDB を使ってデバッグした場合も同様です。

【2020-03-06:追記】

masterには修正が入りました。詳細は末尾の追記を参照してください。

原因

直接の原因は、cargo test--libオプションを付けてテストバイナリをビルドすると、 LLDBのデバッグシンボル情報が書かれたファイル(.dSYM)がtarget/debug/ディレクトリに作成されないことです。

このため、ブレークポイントとして指定したシンボル情報をLLDBが解決できず、デバッガがブレークポイントで止まりません。

rust-lldbを使った場合は、後述のようにエラーが表示されるのですが、 VSCode上でデバッグしている場合、lldb.verboseLoggingを有効にして出力ペインを注意深く見ていないと気づけないと思います。

環境

  • Operating system: macOS Catalina 10.15.3
  • Rust toolchain: stable-x86_64-apple-darwin
  • Rustc version: 1.41.1 (f3e1a954d 2020-02-24)
  • lldb: 9.0.1
  • VSCode: 1.42.1
  • CodeLLDB: 1.5.0

再現手順

  • Cargo.toml
[package]
name = "rust-lldb-workaround"
version = "0.1.0"
edition = "2018"
  • src/lib.rs
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

it_worksにブレークポイントを仕掛けようとすると、下記のように失敗します。

下記のようなシェルスクリプトを実行するとこの様子を確認できます。

#!/bin/bash

rm -rf target
bin=$(cargo test --lib --no-run --message-format=json | jq -r '.executable')
rust-lldb $bin <<- EOF
breakpoint set --name tests::it_works
EOF
   Compiling rust-lldb-workaround v0.1.0 (/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround)
    Finished test [unoptimized + debuginfo] target(s) in 0.51s
(lldb) command script import "/Users/kawahara_taisuke/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/etc/lldb_rust_formatters.py"
(lldb) type summary add --no-value --python-function lldb_rust_formatters.print_val -x ".*" --category Rust
(lldb) type category enable Rust
(lldb) target create "/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9"
Current executable set to '/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9' (x86_64).
(lldb) breakpoint set --name tests::it_works
Breakpoint 1: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.

詳細

binクレートの場合、cargo test.dSYMファイルをtarget/debug/ディレクトリに作成します。 このファイルは実際にはtarget/debug/deps/以下のファイルへのシンボリックリンクです。

% cargo test --bins --no-run --message-format=json 2> /dev/null | jq 'select(.target.kind | contains(["bin"])) | .filenames'
[
  "/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-4aae58342b9c3866",
  "/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-4aae58342b9c3866.dSYM"
]

% ls -l target/debug
total 1720
drwxr-xr-x  2 kawahara_taisuke  staff      64  3  3 13:47 build
drwxr-xr-x  8 kawahara_taisuke  staff     256  3  3 13:47 deps
drwxr-xr-x  2 kawahara_taisuke  staff      64  3  3 13:47 examples
drwxr-xr-x  4 kawahara_taisuke  staff     128  3  3 13:47 incremental
-rwxr-xr-x  2 kawahara_taisuke  staff  874952  3  3 13:47 rust_lldb_workaround-4aae58342b9c3866
-rw-r--r--  1 kawahara_taisuke  staff     282  3  3 13:47 rust_lldb_workaround-4aae58342b9c3866.d
lrwxr-xr-x  1 kawahara_taisuke  staff      47  3  3 13:47 rust_lldb_workaround-4aae58342b9c3866.dSYM -> deps/rust_lldb_workaround-4aae58342b9c3866.dSYM

しかし、libクレートを対象とするとこのシンボリックリンクが作成されません。 rust-lldb及びVSCodeのデバッガ(CodeLLDB)でブレークポイントが効かないのは直接的にはこれが原因です。

% cargo test --lib --no-run --message-format=json 2> /dev/null | jq 'select(.target.kind | contains(["lib"])) | .filenames'
[
  "/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9"
]

% ls -l target/debug
total 1720
drwxr-xr-x  2 kawahara_taisuke  staff      64  3  3 13:49 build
drwxr-xr-x  5 kawahara_taisuke  staff     160  3  3 13:49 deps
drwxr-xr-x  2 kawahara_taisuke  staff      64  3  3 13:49 examples
drwxr-xr-x  3 kawahara_taisuke  staff      96  3  3 13:49 incremental
-rwxr-xr-x  2 kawahara_taisuke  staff  874968  3  3 13:49 rust_lldb_workaround-6e0ca18365abb7b9
-rw-r--r--  1 kawahara_taisuke  staff     201  3  3 13:49 rust_lldb_workaround-6e0ca18365abb7b9.d

% ls -l target/debug/deps
total 1720
-rwxr-xr-x  2 kawahara_taisuke  staff  874968  3  3 13:49 rust_lldb_workaround-6e0ca18365abb7b9
-rw-r--r--  1 kawahara_taisuke  staff     290  3  3 13:49 rust_lldb_workaround-6e0ca18365abb7b9.d
drwxr-xr-x  3 kawahara_taisuke  staff      96  3  3 13:49 rust_lldb_workaround-6e0ca18365abb7b9.dSYM

cargo の実装を見る限りこの挙動は意図したもので、テストの場合はdepsの下を直接実行してもらう想定だったようです。(参考)

https://github.com/rust-lang/cargo/blob/e618d47a1765ca18d1601d4cf891a55a34d23aed/src/cargo/core/compiler/build_context/target_info.rs#L260-L269

対処法としては次の方法が考えられます。

  1. rust-lldbadd-dsymコマンドを実行する。
  2. .dSYMへのリンクをtarget/debug/以下に作る。
  3. cargo test --message-format=jsonの出力するexecutabledepsの下に変更する。

1の方法はCodeLLDBなど使用するVSCode拡張全てに対処が必要で、あまり筋がよくないと思ったので除外しました。

2と3は根本的に解決しようとすると両方ともcargoの動作を変える必要があるのですが、 2についてはcargo testrust-lldbの間にシンボリックリンクを作る処理を挟めば良いので簡単そうです。

この記事では2の方法でのワークアラウンドを示します。

rust-lldbコマンドを直接実行する場合

先ほどのシェルスクリプトを下記のように修正します。

#!/bin/bash

rm -rf target
bin=$(cargo test --lib --no-run --message-format=json | jq -r '.executable')

# .dSYMディレクトリへのシンボリックリンクを作成
(cd target/debug && for d in deps/*.dSYM; do ln -sf $d ./; done)

rust-lldb $bin <<- EOF
breakpoint set --name tests::it_works
EOF

ブレークポイントの設置が成功するようになります。

   Compiling rust-lldb-workaround v0.1.0 (/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround)
    Finished test [unoptimized + debuginfo] target(s) in 0.53s
(lldb) command script import "/Users/kawahara_taisuke/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/etc/lldb_rust_formatters.py"
(lldb) type summary add --no-value --python-function lldb_rust_formatters.print_val -x ".*" --category Rust
(lldb) type category enable Rust
(lldb) target create "/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9"
Current executable set to '/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9' (x86_64).
(lldb) breakpoint set --name tests::it_works
Breakpoint 1: where = rust_lldb_workaround-6e0ca18365abb7b9`rust_lldb_workaround::tests::it_works::h666f078a6b384dfd + 18 at lib.rs:7:8, address = 0x0000000100000cd2

VSCode (CodeLLDB) の場合

launch.jsontasks.json を修正して、cargo testの直後に.dSYMのシンボリックリンクを自動で作成するようにします。

  • .vscode/launch.json

preLaunchTaskを追加した以外はVSCodeからLLVMテンプレートで作ったものそのままです。

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "lldb",
      "request": "launch",
      "name": "Debug unit tests in library 'rust-lldb-workaround'",
      "cargo": {
        "args": [
          "test",
          "--no-run",
          "--lib",
          "--package=rust-lldb-workaround"
        ],
        "filter": {
          "name": "rust-lldb-workaround",
          "kind": "lib"
        }
      },
      "args": [],
      "cwd": "${workspaceFolder}",
      // この行を追加
      "preLaunchTask": "symlink dSYM"
    }
  ]
}
  • .vscode/tasks.json

launch.jsonpreLaunchTaskで参照されているタスクの定義です。 このタスクでは、target/debugディレクトリに移動してtarget/debug/deps/ディレクトリ以下の全ての*.dSYMのシンボリックリンクを作成します。

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "symlink dSYM",
      "type": "shell",
      "command": "sh",
      "args": [
        "-c",
        "cd ${workspaceFolder}/target/debug; for d in deps/*.dSYM; do ln -fs $d ./; done"
      ]
    }
  ]
}

なお Rust Test Lens を使う場合は、上記のタスクを自動で実行することができません。 この場合は事前に一度 cargo test --lib --no-run を実行した後、手動で上記のタスクを実行して .dSYMのシンボリックリンクを作っておく必要があります。

終わりに

この問題はrust-lang/cargo#7960で既に起票されているので、将来的には修正されるかもしれません。

【2020-03-06:追記】

rust-lang/cargo#7965 で修正され、masterにもマージされました。
この修正は2020年4月23日リリース予定の Cargo 1.43 に反映される見込みです。

Railsアプリ開発のためのDocker/Kubernetes入門6 Helm編

RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。 この記事はシリーズ連載記事の第六回です。

はじめにHelmについて簡単に紹介した後、 前回の Kubernetes応用編 で作成したマニフェストをもとにHelmチャートの作り方を説明します。

サンプルコードは全て下記のリポジトリにあります。

https://github.com/kwhrtsk/rails-k8s-demoapp

前提

  • macOSでの作業を前提としています。
  • 使用したツールのバージョンなどは 初回 の記事を参照してください。
  • ツールのインストール手順は 第三回 の記事を参照してください。

準備

まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。 前回とほぼ同じですが、minikubeのメモリを3GBに増やしています。 これは2GBだと後述するSentryの起動に失敗するためです。 すでに2GBで作っている場合はminikubeインスタンスを作り直すか、 Sentryを使ったhelmコマンドのチュートリアルを飛ばしてください。 Railsのサンプルアプリは2GBでも動作します。

また、今回は初めからIngressアドオンを有効にしています。 minikubeインスタンスを削除した場合は忘れずにもう一度有効にしてください。

# サンプルアプリのコードをチェックアウト
$ git clone https://github.com/kwhrtsk/rails-k8s-demoapp.git
$ cd rails-k8s-demoapp

# minikubeを起動
$ minikube start --cpus=3 --memory=3072 --vm-driver=hyperkit --disk-size=12g

# Ingressアドオンをインストール
$ minikube addons enable ingress

# kubernetesのダッシュボードをオープン
$ minikube dashboard

Helmとは

Helm - The Kubernetes Package Manager

アプリケーションに必要なマニフェストファイル一式をパッケージ化して管理するためのツールです。 これを使うと必要なコンテナをまとめてk8sにデプロイすることができます。 このパッケージはChartと呼ばれます。

HelmではマニフェストファイルをGoのtext/template形式でテンプレート化することができ、 イメージのタグやレプリカの数など、マニフェストの一部をパラメータ化することができます。 これらのパラメータは通常デフォルト値を持ち、Chartパッケージをk8sにデプロイ(インストール)する際に上書きできます。

Helmでは公式リポジトリで配布されているChartをk8sクラスタにデプロイすることもできるし、 自分で開発しているプライベートなアプリのChartを作成して、Helmでk8sにデプロイすることもできます。

Helmをk8sにインストールする方法

Helmはクライアント・サーバモデルのアプリケーションです。 k8sクラスタ側にTillerと呼ばれるサーバモジュールをインストールします。 クライアントはhelmコマンドです。

minikubeの場合は下記のコマンドでTillerをインストールできます。

# インストールが完了するまでブロック
$ helm init --wait

Tillerがインストールされているかどうかは下記のコマンドで確認できます。

$ helm version
Client: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}

アンインストールは下記のコマンドです。

$ helm reset

Helmによるパッケージ管理のチュートリアル

この章ではSentry というWebアプリを題材に、 Chartを使ってアプリケーションをminikubeにデプロイするデモをします。

Sentryはアプリケーションのエラー情報を収集してSlackなどに通知するためのサービスです。 SentryのWebサービスはPythonで実装されていますが、Ruby用のクライアントもgemとして配布されており、Railsにも簡単に組み込むことができます。

SaaSを利用することもできるのですが、 公式のDockerイメージが配布されており、 Dockerを使って自前で運用することもできます。

Sentryサービスの内部構成はPythonのWebアプリ、バックグラウンドジョブのためのワーカー、PostgreSQLサーバ、Redisサーバとそれなりに複雑です。 ただ、Helm Chartを使えば比較的簡単にセットアップすることが可能です。

この章ではhelmコマンドのチュートリアルとしてminikube上にSentryをセットアップする手順を示します。

Read More

Railsアプリ開発のためのDocker/Kubernetes入門5 Kubernetes応用編

RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。 この記事はシリーズ連載記事の第五回です。

前回のKubernetes基礎編では、 Step1として第2回Docker Compose/Dockerfile編docker-compose-preview.ymlに相当する構成をDeployment, Service, ConfigMap, Secretの4種のAPIオブジェクトで記述しました。 マニフェストファイル一式はサンプルコードの k8s/manifests-step1/ ディレクトリにあります。

この構成には下記に挙げる三つの制約があります。

  • pumaコンテナを複数起動するとrails db:setupが並列実行されてエラーになる。
  • MySQLやRedisのデータが永続化されていないため、コンテナを停止するとデータも消える。
  • pumaに外部からアクセスするためのエンドポイントを本番環境で運用するのが難しい。

今回はこれらの制約をStep2からStep4で解消していきます。

サンプルコードは全て下記のリポジトリにあります。

https://github.com/kwhrtsk/rails-k8s-demoapp

前提

  • macOSでの作業を前提としています。
  • 使用したツールのバージョンなどは 初回 の記事を参照してください。
  • ツールのインストール手順は 第三回 の記事を参照してください。

準備

まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。 (前回と同じなのですでにやっている人は読み飛ばしてください)

# サンプルアプリのコードをチェックアウト
$ git clone https://github.com/kwhrtsk/rails-k8s-demoapp.git
$ cd rails-k8s-demoapp

# minikubeを起動
$ minikube start --cpus=3 --memory=2048 --vm-driver=hyperkit --disk-size=12g

# kubernetesのダッシュボードをオープン
$ minikube dashboard

Step2: Job

サンプルコードは全て下記のディレクトリにあります。

k8s/manifests-step2/

Step1の構成ではpumaのレプリカの数を1に指定していましたが、 一定以上の性能を出そうとするとレプリカの数は2以上に設定する必要があります。 ところが、このままの構成でpumaコンテナを複数起動すると、rails db:setupが並列実行されてエラーが発生します。

次のような手順でこれを確認できます。

Read More

Railsアプリ開発のためのDocker/Kubernetes入門4 Kubernetes基礎編

RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。 この記事はシリーズ連載記事の第四回です。

今回は以下のサンプルアプリケーションをminikubeにデプロイするためのマニフェストや手順について説明します。

https://github.com/kwhrtsk/rails-k8s-demoapp

前提

  • macOSでの作業を前提としています。
  • 使用したツールのバージョンなどは 初回 の記事を参照してください。
  • ツールのインストール手順は 前回 の記事を参照してください。

準備

まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。 (前回と同じなのですでにやっている人は読み飛ばしてください)

# サンプルアプリのコードをチェックアウト
$ git clone https://github.com/kwhrtsk/rails-k8s-demoapp.git
$ cd rails-k8s-demoapp

# minikubeを起動
$ minikube start --cpus=3 --memory=2048 --vm-driver=hyperkit --disk-size=12g

# kubernetesのダッシュボードをオープン
$ minikube dashboard

概要

前回は下記について説明しました。

  • APIオブジェクトを定義するマニフェストファイルをYAML形式で記述する方法
  • マニフェストファイルとkubectlコマンドを使ってAPIオブジェクトを管理する方法

今回は実際にRailsアプリをMySQLやRedisなどのミドルウェアも含めて丸ごとk8sクラスタ上にデプロイするための手順を説明します。 一度に多種のAPIオブジェクトを使うと全体を理解するのが難しくなるので、 4つのステップに分けて進めます。

Step1では、第2回Docker Compose/Dockerfile編docker-compose-preview.ymlに相当する構成をできるだけ少ないAPIオブジェクトで簡潔に記述します。 この時点では簡潔さと引き換えにいくつかの制約がありますが、それらをStep2からStep4で解消していきます。

今回のKubernetes基礎編ではStep1を説明し、 次回のKubernetes応用編でStep2以降を説明します。

Read More

Railsアプリ開発のためのDocker/Kubernetes入門3 Kubernetes入門編

RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。 この記事はシリーズ連載記事の第三回です。

今回は下記について書きます。

  • 最小限のk8s入門
  • minikubeの使い方
  • kubectlコマンドのチュートリアル

前提

  • macOSでの作業を前提としています。
  • 使用したツールのバージョンなどは 初回 の記事を参照してください。

はじめに

Kubernetesには膨大な機能があるので、最初から汎用的な使い方を学ぼうとすると挫折しがちです。 このドキュメントでは、紹介する機能や概念を「初心者がRailsアプリを動かすために必要な機能」という観点で限定し、 かつ段階的に説明していきます。

まずは minikubeを使ってmacOS上の仮想マシンにスタンドアローンのk8sクラスタを作り、 そこに前回 Docker Compose/Dockerfile編 で使用したサンプルアプリを デプロイする具体的な手順を示します。

紹介するサンプルコードは全て下記のリポジトリにあります。

https://github.com/kwhrtsk/rails-k8s-demoapp

開発環境の構築

下記のツールを使います。

  • minikube k8sクラスタをローカルVM上に構築するためのツールです。
  • hyperkitのドライバ minikubeがmacOS上でVMを操作するために必要です。
  • kubectl k8sクラスタを操作するためのCLIツールです。
  • helm k8sのマニフェストをパッケージとして管理するためのツールです。第六回 Helm編で使用します。
  • stern k8s上のコンテナのログを見るためのツールです。必須ではありませんがあると便利です。
  • GNU Make XCode付属のものを使います。無くても大丈夫です。

minikubeとkubectlとsternはHomebrew(cask)でインストールできます。

$ brew cask install minikube
$ brew install kubernetes-cli kubernetes-helm
$ brew install stern

hyperkitのドライバは次の手順でインストールしてください。参考

$ curl -LO https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-hyperkit \
&& chmod +x docker-machine-driver-hyperkit \
&& sudo mv docker-machine-driver-hyperkit /usr/local/bin/ \
&& sudo chown root:wheel /usr/local/bin/docker-machine-driver-hyperkit \
&& sudo chmod u+s /usr/local/bin/docker-machine-driver-hyperkit

macOS版のminikubeでは、hyperkitの代わりにVirtualBoxを使うこともできます。 全ての手順は両方で動作を確認していますが、hyperkitの方が高速です。 筆者の環境ではRailsアプリやMySQLなど一式k8sにデプロイするのにかかる時間はVirtualBoxだとおおよそ2分20秒前後、 hyperkitだと50秒前後でした。

最小限のKubernetes(k8s)入門

Kubernetesとは

k8sはDocker Composeと同じく複数のコンテナを管理するためのツールです。

Docker Composeでは一つのホスト上に複数のコンテナを起動することができましたが、 k8sでは複数のDockerサーバで一つのクラスタを構成し、その上に複数のコンテナを分散配置することができます。

また、Docker Composeは単にコンテナの起動や停止だけでなく、コンテナ間通信やボリュームの管理を行うことができましたが、 k8sはさらにロードバランサや定期実行ジョブなど実環境で必要になる多様な機能を抽象化し管理することができます。

minikubeとは

k8sをセットアップして使えるようにするためには色々な方法があります。代表的なものをいくつか紹介します。

  • GKE: GCP上のマネージドサービス
  • kops: AWS上にec2インスタンスを複数起動してk8sクラスタとして構成するツール
  • AKS: Azure上のマネージドサービス
  • kubespray: オンプレミスを含む任意の環境にk8sクラスタをセットアップするツール

本番環境でk8sを運用するためには上記のようなものを使う必要がありますが、 k8sを試してみるだけなら手元の環境で動作するminikubeを使うのが一番簡単です。

minikubeは端末上のVMへk8sをインストールして簡単に使えるようにするためのツールです。 VMドライバとしてはVirtualBox、hyperkit、KVMなどがサポートされています。

minikube/drivers.md

macOS上で試すのであれば VirtualBox か hyperkit が簡単です。 デフォルトでは VirtualBox が選択されるようですが、hyperkitの方が高速なのでこちらを使うのがおすすめです。

hyperkitはmacOS組み込みの仮想化フレームワーク 「Hypervisor Framework」を使うためのツールで、Docker for Macでも使われています。

次節以降ではminikubeの基本的な使い方を説明します。

Read More

Railsアプリ開発のためのDocker/Kubernetes入門2 Docker Compose/Dockerfile編

RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。 この記事はシリーズ連載記事の第二回です。

今回は下記について書きます。

  • 最小限のDocker Compose入門
  • Docker Composeを使った各種ミドルウェアのインストールと管理
  • RailsアプリケーションのDockerイメージの作り方
  • Docker Composeによるローカルプレビュー環境の構築

サンプルアプリケーション

簡単なRailsアプリを例に、Docker Composeの使い方やDockerfileの書き方を説明します。 このサンプルアプリrails-k8s-demoappのコードは下記のリポジトリに置いています。

https://github.com/kwhrtsk/rails-k8s-demoapp

rails-k8s-demoappは「フォームでメッセージを送信すると画面上のリストに追加して表示する」だけの小さなアプリです。

仕様は極小ですが、できるだけ一般的なRailsアプリの構成に近くなるように下記の要件を満たすものにしています。

  • ミドルウェアとしてMySQLとRedis(SidekiqとActionCableのため)を使用
  • ActiveJobを使用: Pumaの他にSidekiqプロセスの起動が必要なケースを想定
    • 新しいメッセージが追加されると、3秒後に逆順の文字列をさらにメッセージとして追加するようなジョブをSidekiqで実行します。
  • ActionCableを使用: websocketが必要なケースを想定
    • 新しいメッセージが追加された時にActionCableでブラウザに通知を行いページリロードさせます。
  • Webpackerを使用: アセットのビルドにnodeとyarnが必要なケースを想定
    • フロントエンドのコードはTypeScriptで書いています。
    • webpackでBootstrap v4を組み込んでいます。

下記のコマンドで動作を確認することができます。RubyやRailsの開発環境は必要ありません。 gitとDockerがインストールされていれば動きます。

$ git clone https://github.com/kwhrtsk/rails-k8s-demoapp.git
$ cd rails-k8s-demoapp
$ docker-compose -f docker-compose-preview.yml up -d
$ open http://localhost:3000/

docker-composeコマンドの使い方やサンプルアプリケーションで使っているdocker-compose.ymlDockerfileについては順に説明します。

Read More

Railsアプリ開発のためのDocker/Kubernetes入門1 Docker編

RailsアプリケーションをKubernetes(k8s)で運用できるようにするための手順を書きます。

概要

この記事はシリーズ連載記事の第一回です。シリーズ全体の概要は次の通りです。

はじめに、DockerやKubernetesの概要について簡単に説明しつつ、 dockerコマンドやkubectlコマンドの基本的な使い方をチュートリアル風味で紹介します。

次に、ごく小さな仕様の具体的なRailsアプリを題材として、 Docker Composeの構成ファイルやKubernetesのマニフェストの具体的なサンプルと使い方を示し、 最終的にHelmを使ってKubernetesクラスタにデプロイする方法を説明します。

DockerとKubernetesの全機能を網羅的に学ぼうとすると膨大な時間が必要になるので、 この記事では「RailsアプリをKubernetesで運用すること」にフォーカスして一つ一つの概念や機能の詳細は必要最小限に絞り、 より詳細な資料へのポインタを置くに留めます。

シリーズ全体の構成は次の通りです。

  • 第一回 Docker編
    • 最小限のDocker入門
    • dockerコマンドのチュートリアル
  • 第二回 Docker Compose/Dockerfile編
    • 最小限のDocker Compose入門
    • Docker Composeを使った各種ミドルウェアのインストールと管理
    • RailsアプリケーションのDockerイメージの作り方
    • Docker Composeによるローカルプレビュー環境の構築
  • 第三回 Kubernetes入門編
    • 最小限のk8s入門
    • minikubeの使い方
    • kubectlコマンドのチュートリアル
  • 第四回 Kubernetes基礎編
    • kubectlコマンドとYAML形式のマニフェストファイルでRailsアプリをk8sにデプロイする方法
  • 第五回 Kubernetes応用編
    • 基礎編で残した課題の解決編
  • 第六回 Helm編
    • 最小限のHelm入門
    • HelmでRailsアプリをk8sにデプロイする方法

全てのサンプルコードはここにあります。

https://github.com/kwhrtsk/rails-k8s-demoapp

前提

  • 各種ツール類のインストール手順はmacOSでの作業を前提としています。
  • HomebrewHomebrew-Caskを使います。
    • Homebrew-Caskのインストールはbrew tap caskroom/cask
  • Docker for Macを使います。
  • k8s関連ツールのインストール手順は後述します。
  • ツールのインストール手順を除けば、Linuxでもおおよそ同じ手順で動作を確認できると思います。

それぞれ下記のバージョンで動作を確認しています。

名称 バージョン
macOS High Sierra(10.13.4)
Docker for Mac 18.0.3.1-ce-mac65
Ruby 2.5.1
Rails 5.2.0
minikube 0.27.0 (k8s 1.10.0)
kubectl 1.10.2
helm 2.9.1
hyperkit v0.20171204-60-g0e5b6b
virtualbox 5.2.8

最小限のDocker入門

Docker初心者がRailsアプリをk8sで動かすまでに最低限必要なDockerについての知識とdockerコマンドの操作方法を書きます。 Rails固有のトピックを知りたい人は Docker Compose/Dockerfile編 までスキップしてください。

Dockerとは

LXC(Linux Containers)と呼ばれる技術を使ってアプリケーションの開発やデプロイを行うためのツール及びプラットフォームです。 LXCやDockerについて解説されたドキュメントは山ほどあるのでここでは詳細は割愛します。

dockerのコマンド体系について

2017年1月にリリースされたDocker 1.13で docker コマンドの構成が整理されました。 アナウンスは Introducing Docker 1.13 - Docker BlogCLI restructuredの部分です。

「旧コマンドは引き続きサポートするが、新コマンドの使用を勧める」とのことなので このドキュメントでは新しいコマンド体系で説明します。 ただ、古いドキュメントには旧コマンド体系で書かれているものも多いため、 対応する旧コマンドについても紹介します。

このドキュメントで紹介するコマンドの新旧対応は下記の通りです。 基本的にはimageかcontainerのサブコマンドへの移動ですが、一部違うものがあるので太字にしています。

旧コマンド 新コマンド 説明
docker pull docker image pull イメージを取得する
docker images docker image ls イメージの一覧を表示する
docker rmi docker image rm イメージを削除する
docker run docker container run コンテナを起動する
docker ps docker container ls コンテナの一覧を表示する
docker exec docker container exec 起動中のコンテナで新しいコマンドを実行する
docker logs docker container logs コンテナのログを表示する
docker rm docker container rm コンテナを削除する

以降の節で順にこれらのコマンドの使い方を説明します。見出しの括弧は旧コマンドです。

また、各節の終わりにリファレンスへのリンクを置いていますが、 少なくとも本稿の執筆時点においては旧コマンドの方が詳細に書かれているので、必要に応じてそちらも参照してください。 なお日本語版のドキュメントにはまだ新コマンド版のリファレンスはありません。

シェルの補完について

dockerのサブコマンドは、 861162a44 のようなハッシュ値や、 romantic_neumann のようにランダムな英単語の組み合わせで自動生成されたコンテナ名をパラメータとして受け取ります。

Docker for Macにはbashやzshでコマンドの補完を行うためのスクリプトが同梱されていますが、 ただインストールするだけでは有効になりません。 補完のための設定については別に記事を書いたのでこちらを参照してください。

bash/zshとfzfでDocker関連コマンドの補完を行う方法

docker image pull (docker pull): イメージを取得する

イメージとはコンテナの雛形です。docker image pullコマンドでリポジトリとタグを指定し、イメージを取得します。 MySQLの公式リポジトリから5.7.21のタグが付いたイメージを取得する場合は下記のようにします。

$ docker image pull mysql:5.7.21
5.7.21: Pulling from library/mysql
2a72cbf407d6: Pull complete
38680a9b47a8: Pull complete
4c732aa0eb1b: Pull complete
c5317a34eddd: Pull complete
f92be680366c: Pull complete
e8ecd8bec5ab: Pull complete
2a650284a6a8: Pull complete
5b5108d08c6d: Pull complete
beaff1261757: Pull complete
c1a55c6375b5: Pull complete
8181cde51c65: Pull complete
Digest: sha256:691c55aabb3c4e3b89b953dd2f022f7ea845e5443954767d321d5f5fa394e28c
Status: Downloaded newer image for mysql:5.7.21

タグは、多くの場合バージョンを指します。ただし、ここでいうバージョンはイメージのバージョンではなく、 アプリケーションのバージョンであることが多いため、 同じタグを指定してもタイミングによって違うイメージを取得するケースがある点に注意が必要です。

例えばmysql:5.7は現時点ではmysql:5.7.21と同じイメージを指していますが、以前はmysql:5.7.20を指していました。 タグを指定しないとlatestというタグを指定したとみなされます。

リポジトリを管理しているサービスをレジストリと呼びます。 MySQLやRedisなどの主要なプロダクトの公式イメージは Docker Hub というレジストリで配布されており、 上記の例ではDocker Hub上のリポジトリからイメージを取得しています。 Docker Hub上のリポジトリは下記のページで探すことができます。

https://hub.docker.com/explore/

Docker Hub以外のレジストリを使う場合は下記のようにリポジトリの前にレジストリのホスト名とポート番号を書きます。 例えばローカルレジストリ myregistry.local:5000 から testing/test-image を取得する場合は下記のようになります。

$ docker image pull myregistry.local:5000/testing/test-image

Read More

bash/zshとfzfでDocker関連コマンドの補完を行う方法

dockerコマンドはパラメータにコンテナIDやイメージIDを取るケースがあって入力が面倒です。 公式のbash/zsh用補完スクリプトを使うと各種ID類も補完できるようになるのですが、 Docker for Macをインストールするだけではそれらのスクリプトは有効にならないので、使い方を解説します。

また、fzfを使って候補のフィルタリングや複数選択を楽に行えるようにする方法についても説明します。

最終的にはこんな感じになります。

dockerとdocker-composeの補完スクリプト

Docker for Macにはbashとzsh用の補完スクリプトが同梱されています。

% ls -1 /Applications/Docker.app/Contents/Resources/etc/
docker-compose.bash-completion
docker-compose.zsh-completion
docker-machine.bash-completion
docker-machine.zsh-completion
docker.bash-completion
docker.fish-completion
docker.zsh-completion

適切に設定すれば下記のようにサブコマンドやイメージIDなどを補完できます。

Read More

Rails 5.2.0 で問題の起きるgem(simple_form, annotate)

Rails 5.2.0が正式にリリースされましたね。めでたい。

いつものことですが、2018-04-11現在、Rails 5.2.0だと正常に動作しないgemがあります。とりあえず見つけたものをメモしておきます。

simple_form

Rails 5.2.0 upgrade causes “Undefined method wrapper for SimpleForm:Module” error Issue #1568 · plataformatec/simple_form

Gemfileでsimple_formのバージョンを指定していない場合、railsのバージョンを5.2.0に上げてbundle updateしたらsimple_formのバージョンが3.5.1から1.4.1まで下がるという現象が起きていました。上記はそのために引き起こされるエラーです。

単にgemspecの要求バージョンが厳しかったみたいで、このブログを書いている間に 4.0.0 がリリースされて解決しました。

https://github.com/plataformatec/simple_form/blob/master/CHANGELOG.md#400

3.5系でバージョンをロックしている人は4.0.0を試してみましょう。

# Gemfile

# gem "simple_form", "~> 3.5.1"
gem "simple_form", "~> 4.0.0"

annotate

Annotate no longer works with Rails 5.2 models Issue #538 · ctran/annotate_models

./bin/rails annotate_models または ./bin/rails db:migrate など、モデルのアノテーションを更新する際に下記のようなエラーが起きる場合があります。

Unable to annotate app/models/author.rb: can't modify frozen String
Unable to annotate app/models/author.rb: no implicit conversion of nil into Array

暫定的な措置として、設定でインデックスに関する情報の記入を無効化すればエラーを回避できます。

diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake
index 9088128..9c2f5d5 100644
--- a/lib/tasks/auto_annotate_models.rake
+++ b/lib/tasks/auto_annotate_models.rake
@@ -16,7 +16,7 @@ if Rails.env.development?
       'position_in_serializer'    => 'before',
       'show_foreign_keys'         => 'true',
       'show_complete_foreign_keys' => 'false',
-      'show_indexes'              => 'true',
+      'show_indexes'              => 'false',
       'simple_indexes'            => 'false',
       'model_dir'                 => 'app/models',
       'root_dir'                  => '',

他にも何か見つけたら更新していきます。

chef社のVagrant Boxがbento organizationに移行してた

Vagrantを使うときに、どこの馬の骨ともしれないboxは使いたくないので chef 社が公式にサポートしてるboxを使っていました。

# Vagrantfile
Vagrant.configure(2) do |config|
  # こんなの
  config.vm.box = "chef/centos-7.1"
end

8/27か28あたりに配布されなくなったようです。

https://vagrantcloud.com/chef

跡地でBentoというorganizationへ誘導しています。

https://atlas.hashicorp.com/bento/

BentoというのもChef社にメンテナンスされているプロジェクトで、PackerテンプレートでVagrantのbase boxイメージを作ることができます。

彼ら自身がchefをテストするためのboxイメージを作るためにも使っているとのこと。

https://github.com/chef/bento

今後はこんな風にBento boxを使うことにします。

# Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "bento/centos-7.1"
end

(おそらく公式の)BentoのTwitterアイコンがすごく弁当箱です。集中線がジワジワくる。

https://twitter.com/bento_chef