<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>割り箸ポテチ</title>
  
  <subtitle>ソフトウェアエンジニアリング一般についてのメモです。</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://chopschips.net/"/>
  <updated>2020-03-06T12:25:26.349Z</updated>
  <id>https://chopschips.net/</id>
  
  <author>
    <name>Kawahara Taisuke / 河原太介</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>VSCodeでRustのテストコードをデバッグできない問題</title>
    <link href="https://chopschips.net/blog/2020/03/03/rust-lldb-workaround/"/>
    <id>https://chopschips.net/blog/2020/03/03/rust-lldb-workaround/</id>
    <published>2020-03-03T07:45:00.000Z</published>
    <updated>2020-03-06T12:25:26.349Z</updated>
    
    <content type="html"><![CDATA[<p>2018エディションにNLLも入って久しいし、<a href="https://github.com/hyperium/tonic" target="_blank" rel="noopener">tonic</a>(async/await構文でgRPCサービスを書けるライブラリ)からベータが取れたぞということで久しぶりにRustやろうという気持ちが高まってきました(2年ぶり3回目)。</p><p>まずは開発環境を更新せねばということで諸々アップデートして動作を確認していたのですが、相変わらずVSCode + LLDBという構成だと<strong>libクレートの単体テスト</strong>をデバッグするときにブレークポイントが効かない。<strong>binクレートの単体テストだと効く。</strong>なぜか日本語のブログではこの問題に触れているのを見たことがないんですよね(そういう入門記事はだいたいサンプルがbinクレートなので、この問題を踏むのはもっと後のステップではあるんですが)。</p><p>私の環境固有の問題かなとも少し思ったんですが、GitHubのIssueを漁ってみると同じ問題を踏んでそうな人をちらほら見かけたので、今回はちょっと時間を使って原因を調べてみることにしました。</p><p>問題の再現手順とかワークアラウンドのスクリプトは下記のリポジトリにも置いてあります。</p><p><a href="https://github.com/kwhrtsk/rust-lldb-workaround" target="_blank" rel="noopener">https://github.com/kwhrtsk/rust-lldb-workaround</a></p><p>解決方法だけ知りたい人は<a href="#VSCode-CodeLLDB-の場合">VSCode (CodeLLDB) の場合</a>を参照してください。</p><h1 id="問題の概要"><a href="#問題の概要" class="headerlink" title="問題の概要"></a>問題の概要</h1><p>macOSにおいて、<code>rust-lldb</code> でlibクレートのテストをデバッグしようとしたときに、ブレークポイントが効きません。VSCodeで <a href="https://github.com/vadimcn/vscode-lldb" target="_blank" rel="noopener">CodeLLDB</a> を使ってデバッグした場合も同様です。</p><p><strong>【2020-03-06:追記】</strong></p><p>masterには修正が入りました。詳細は末尾の追記を参照してください。</p><h1 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h1><p>直接の原因は、<code>cargo test</code>に<code>--lib</code>オプションを付けてテストバイナリをビルドすると、LLDBのデバッグシンボル情報が書かれたファイル(<code>.dSYM</code>)が<code>target/debug/</code>ディレクトリに作成されないことです。</p><p>このため、ブレークポイントとして指定したシンボル情報をLLDBが解決できず、デバッガがブレークポイントで止まりません。</p><p><code>rust-lldb</code>を使った場合は、後述のようにエラーが表示されるのですが、VSCode上でデバッグしている場合、<code>lldb.verboseLogging</code>を有効にして出力ペインを注意深く見ていないと気づけないと思います。</p><h1 id="環境"><a href="#環境" class="headerlink" title="環境"></a>環境</h1><ul><li><strong>Operating system:</strong> macOS Catalina 10.15.3</li><li><strong>Rust toolchain:</strong> stable-x86_64-apple-darwin</li><li><strong>Rustc version:</strong> 1.41.1 (f3e1a954d 2020-02-24)</li><li><strong>lldb:</strong> 9.0.1</li><li><strong>VSCode:</strong> 1.42.1</li><li><strong>CodeLLDB:</strong> 1.5.0</li></ul><h1 id="再現手順"><a href="#再現手順" class="headerlink" title="再現手順"></a>再現手順</h1><ul><li><code>Cargo.toml</code></li></ul><pre class="line-numbers language-toml"><code class="language-toml">[package]name = "rust-lldb-workaround"version = "0.1.0"edition = "2018"<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>src/lib.rs</code></li></ul><pre class="line-numbers language-rust"><code class="language-rust"><span class="token attribute attr-name">#[cfg(test)]</span><span class="token keyword">mod</span> tests <span class="token punctuation">{</span>    <span class="token attribute attr-name">#[test]</span>    <span class="token keyword">fn</span> <span class="token function">it_works</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token function">assert_eq!</span><span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>it_works</code>にブレークポイントを仕掛けようとすると、下記のように失敗します。</p><p>下記のようなシェルスクリプトを実行するとこの様子を確認できます。</p><pre class="line-numbers language-sh"><code class="language-sh">#!/bin/bashrm -rf targetbin=$(cargo test --lib --no-run --message-format=json | jq -r '.executable')rust-lldb $bin <<- EOFbreakpoint set --name tests::it_worksEOF<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre><code>   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 &quot;/Users/kawahara_taisuke/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/etc/lldb_rust_formatters.py&quot;(lldb) type summary add --no-value --python-function lldb_rust_formatters.print_val -x &quot;.*&quot; --category Rust(lldb) type category enable Rust(lldb) target create &quot;/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9&quot;Current executable set to &#39;/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9&#39; (x86_64).(lldb) breakpoint set --name tests::it_worksBreakpoint 1: no locations (pending).WARNING:  Unable to resolve breakpoint to any actual locations.</code></pre><h1 id="詳細"><a href="#詳細" class="headerlink" title="詳細"></a>詳細</h1><p>binクレートの場合、<code>cargo test</code>は<code>.dSYM</code>ファイルを<code>target/debug/</code>ディレクトリに作成します。このファイルは実際には<code>target/debug/deps/</code>以下のファイルへのシンボリックリンクです。</p><pre><code>% cargo test --bins --no-run --message-format=json 2&gt; /dev/null | jq &#39;select(.target.kind | contains([&quot;bin&quot;])) | .filenames&#39;[  &quot;/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-4aae58342b9c3866&quot;,  &quot;/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-4aae58342b9c3866.dSYM&quot;]% ls -l target/debugtotal 1720drwxr-xr-x  2 kawahara_taisuke  staff      64  3  3 13:47 builddrwxr-xr-x  8 kawahara_taisuke  staff     256  3  3 13:47 depsdrwxr-xr-x  2 kawahara_taisuke  staff      64  3  3 13:47 examplesdrwxr-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.dlrwxr-xr-x  1 kawahara_taisuke  staff      47  3  3 13:47 rust_lldb_workaround-4aae58342b9c3866.dSYM -&gt; deps/rust_lldb_workaround-4aae58342b9c3866.dSYM</code></pre><p>しかし、libクレートを対象とするとこのシンボリックリンクが作成されません。<code>rust-lldb</code>及びVSCodeのデバッガ(CodeLLDB)でブレークポイントが効かないのは直接的にはこれが原因です。</p><pre><code>% cargo test --lib --no-run --message-format=json 2&gt; /dev/null | jq &#39;select(.target.kind | contains([&quot;lib&quot;])) | .filenames&#39;[  &quot;/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9&quot;]% ls -l target/debugtotal 1720drwxr-xr-x  2 kawahara_taisuke  staff      64  3  3 13:49 builddrwxr-xr-x  5 kawahara_taisuke  staff     160  3  3 13:49 depsdrwxr-xr-x  2 kawahara_taisuke  staff      64  3  3 13:49 examplesdrwxr-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/depstotal 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.ddrwxr-xr-x  3 kawahara_taisuke  staff      96  3  3 13:49 rust_lldb_workaround-6e0ca18365abb7b9.dSYM</code></pre><p><code>cargo</code> の実装を見る限りこの挙動は意図したもので、テストの場合は<code>deps</code>の下を直接実行してもらう想定だったようです。(<a href="https://github.com/rust-lang/cargo/issues/7960" target="_blank" rel="noopener">参考</a>)</p><p><a href="https://github.com/rust-lang/cargo/blob/e618d47a1765ca18d1601d4cf891a55a34d23aed/src/cargo/core/compiler/build_context/target_info.rs#L260-L269" target="_blank" rel="noopener">https://github.com/rust-lang/cargo/blob/e618d47a1765ca18d1601d4cf891a55a34d23aed/src/cargo/core/compiler/build_context/target_info.rs#L260-L269</a></p><p>対処法としては次の方法が考えられます。</p><ol><li><code>rust-lldb</code>で<code>add-dsym</code>コマンドを実行する。</li><li><code>.dSYM</code>へのリンクを<code>target/debug/</code>以下に作る。</li><li><code>cargo test --message-format=json</code>の出力する<code>executable</code>を<code>deps</code>の下に変更する。</li></ol><p>1の方法はCodeLLDBなど使用するVSCode拡張全てに対処が必要で、あまり筋がよくないと思ったので除外しました。</p><p>2と3は根本的に解決しようとすると両方とも<code>cargo</code>の動作を変える必要があるのですが、2については<code>cargo test</code>と<code>rust-lldb</code>の間にシンボリックリンクを作る処理を挟めば良いので簡単そうです。</p><p>この記事では2の方法でのワークアラウンドを示します。</p><h1 id="rust-lldbコマンドを直接実行する場合"><a href="#rust-lldbコマンドを直接実行する場合" class="headerlink" title="rust-lldbコマンドを直接実行する場合"></a><code>rust-lldb</code>コマンドを直接実行する場合</h1><p>先ほどのシェルスクリプトを下記のように修正します。</p><pre class="line-numbers language-sh"><code class="language-sh">#!/bin/bashrm -rf targetbin=$(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 <<- EOFbreakpoint set --name tests::it_worksEOF<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>ブレークポイントの設置が成功するようになります。</p><pre><code>   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 &quot;/Users/kawahara_taisuke/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/etc/lldb_rust_formatters.py&quot;(lldb) type summary add --no-value --python-function lldb_rust_formatters.print_val -x &quot;.*&quot; --category Rust(lldb) type category enable Rust(lldb) target create &quot;/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9&quot;Current executable set to &#39;/Users/kawahara_taisuke/.ghq/github.com/kwhrtsk/rust-lldb-workaround/target/debug/rust_lldb_workaround-6e0ca18365abb7b9&#39; (x86_64).(lldb) breakpoint set --name tests::it_worksBreakpoint 1: where = rust_lldb_workaround-6e0ca18365abb7b9`rust_lldb_workaround::tests::it_works::h666f078a6b384dfd + 18 at lib.rs:7:8, address = 0x0000000100000cd2</code></pre><h1 id="VSCode-CodeLLDB-の場合"><a href="#VSCode-CodeLLDB-の場合" class="headerlink" title="VSCode (CodeLLDB) の場合"></a>VSCode (CodeLLDB) の場合</h1><p><code>launch.json</code> と <code>tasks.json</code> を修正して、<code>cargo test</code>の直後に<code>.dSYM</code>のシンボリックリンクを自動で作成するようにします。</p><ul><li><code>.vscode/launch.json</code></li></ul><p><code>preLaunchTask</code>を追加した以外はVSCodeから<code>LLVM</code>テンプレートで作ったものそのままです。</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">{</span>  <span class="token property">"version"</span><span class="token operator">:</span> <span class="token string">"0.2.0"</span><span class="token punctuation">,</span>  <span class="token property">"configurations"</span><span class="token operator">:</span> <span class="token punctuation">[</span>    <span class="token punctuation">{</span>      <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"lldb"</span><span class="token punctuation">,</span>      <span class="token property">"request"</span><span class="token operator">:</span> <span class="token string">"launch"</span><span class="token punctuation">,</span>      <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Debug unit tests in library 'rust-lldb-workaround'"</span><span class="token punctuation">,</span>      <span class="token property">"cargo"</span><span class="token operator">:</span> <span class="token punctuation">{</span>        <span class="token property">"args"</span><span class="token operator">:</span> <span class="token punctuation">[</span>          <span class="token string">"test"</span><span class="token punctuation">,</span>          <span class="token string">"--no-run"</span><span class="token punctuation">,</span>          <span class="token string">"--lib"</span><span class="token punctuation">,</span>          <span class="token string">"--package=rust-lldb-workaround"</span>        <span class="token punctuation">]</span><span class="token punctuation">,</span>        <span class="token property">"filter"</span><span class="token operator">:</span> <span class="token punctuation">{</span>          <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"rust-lldb-workaround"</span><span class="token punctuation">,</span>          <span class="token property">"kind"</span><span class="token operator">:</span> <span class="token string">"lib"</span>        <span class="token punctuation">}</span>      <span class="token punctuation">}</span><span class="token punctuation">,</span>      <span class="token property">"args"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>      <span class="token property">"cwd"</span><span class="token operator">:</span> <span class="token string">"${workspaceFolder}"</span><span class="token punctuation">,</span>      // この行を追加      <span class="token property">"preLaunchTask"</span><span class="token operator">:</span> <span class="token string">"symlink dSYM"</span>    <span class="token punctuation">}</span>  <span class="token punctuation">]</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>.vscode/tasks.json</code></li></ul><p><code>launch.json</code>の<code>preLaunchTask</code>で参照されているタスクの定義です。このタスクでは、<code>target/debug</code>ディレクトリに移動して<code>target/debug/deps/</code>ディレクトリ以下の全ての<code>*.dSYM</code>のシンボリックリンクを作成します。</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">{</span>  <span class="token property">"version"</span><span class="token operator">:</span> <span class="token string">"2.0.0"</span><span class="token punctuation">,</span>  <span class="token property">"tasks"</span><span class="token operator">:</span> <span class="token punctuation">[</span>    <span class="token punctuation">{</span>      <span class="token property">"label"</span><span class="token operator">:</span> <span class="token string">"symlink dSYM"</span><span class="token punctuation">,</span>      <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"shell"</span><span class="token punctuation">,</span>      <span class="token property">"command"</span><span class="token operator">:</span> <span class="token string">"sh"</span><span class="token punctuation">,</span>      <span class="token property">"args"</span><span class="token operator">:</span> <span class="token punctuation">[</span>        <span class="token string">"-c"</span><span class="token punctuation">,</span>        <span class="token string">"cd ${workspaceFolder}/target/debug; for d in deps/*.dSYM; do ln -fs $d ./; done"</span>      <span class="token punctuation">]</span>    <span class="token punctuation">}</span>  <span class="token punctuation">]</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>なお <a href="https://github.com/hdevalke/rust-test-lens" target="_blank" rel="noopener">Rust Test Lens</a> を使う場合は、上記のタスクを自動で実行することができません。この場合は事前に一度 <code>cargo test --lib --no-run</code> を実行した後、手動で上記のタスクを実行して<code>.dSYM</code>のシンボリックリンクを作っておく必要があります。</p><h1 id="終わりに"><a href="#終わりに" class="headerlink" title="終わりに"></a>終わりに</h1><p>この問題は<a href="https://github.com/rust-lang/cargo/issues/7960" target="_blank" rel="noopener">rust-lang/cargo#7960</a>で既に起票されているので、将来的には修正されるかもしれません。</p><p><strong>【2020-03-06:追記】</strong></p><p><a href="https://github.com/rust-lang/cargo/pull/7965" target="_blank" rel="noopener">rust-lang/cargo#7965</a> で修正され、masterにもマージされました。<br>この修正は2020年4月23日リリース予定の <a href="https://github.com/rust-lang/cargo/blob/master/CHANGELOG.md#cargo-143-2020-04-23" target="_blank" rel="noopener">Cargo 1.43</a> に反映される見込みです。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;2018エディションにNLLも入って久しいし、&lt;a href=&quot;https://github.com/hyperium/tonic&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tonic&lt;/a&gt;(async/await構文でgRPCサービスを書けるライブ
      
    
    </summary>
    
      <category term="開発環境" scheme="https://chopschips.net/categories/%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83/"/>
    
    
      <category term="rust" scheme="https://chopschips.net/tags/rust/"/>
    
      <category term="lldb" scheme="https://chopschips.net/tags/lldb/"/>
    
      <category term="vscode" scheme="https://chopschips.net/tags/vscode/"/>
    
  </entry>
  
  <entry>
    <title>Railsアプリ開発のためのDocker/Kubernetes入門6 Helm編</title>
    <link href="https://chopschips.net/blog/2018/05/30/helm-with-rails/"/>
    <id>https://chopschips.net/blog/2018/05/30/helm-with-rails/</id>
    <published>2018-05-30T13:50:00.000Z</published>
    <updated>2018-05-31T15:17:03.816Z</updated>
    
    <content type="html"><![CDATA[<p>RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。この記事はシリーズ連載記事の第六回です。</p><ul><li>第一回 <a href="/blog/2018/05/30/docker-with-rails/">Docker編</a></li><li>第二回 <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a></li><li>第三回 <a href="/blog/2018/05/30/kubernetes-tutorial/">Kubernetes入門編</a></li><li>第四回 <a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes基礎編</a></li><li>第五回 <a href="/blog/2018/05/30/practical-kubernetes-with-rails/">Kubernetes応用編</a></li><li>第六回 <a href="/blog/2018/05/30/helm-with-rails/">Helm編</a></li></ul><p>はじめにHelmについて簡単に紹介した後、前回の <a href="/blog/2018/05/30/practical-kubernetes-with-rails/">Kubernetes応用編</a>で作成したマニフェストをもとにHelmチャートの作り方を説明します。</p><p>サンプルコードは全て下記のリポジトリにあります。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp" target="_blank" rel="noopener">https://github.com/kwhrtsk/rails-k8s-demoapp</a></p><h1 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h1><ul><li>macOSでの作業を前提としています。</li><li>使用したツールのバージョンなどは <a href="/blog/2018/05/30/docker-with-rails/#前提">初回</a> の記事を参照してください。</li><li>ツールのインストール手順は <a href="/blog/2018/05/30/kubernetes-tutorial/#開発環境の構築">第三回</a> の記事を参照してください。</li></ul><h1 id="準備"><a href="#準備" class="headerlink" title="準備"></a>準備</h1><p>まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。前回とほぼ同じですが、minikubeのメモリを3GBに増やしています。これは2GBだと後述するSentryの起動に失敗するためです。すでに2GBで作っている場合はminikubeインスタンスを作り直すか、Sentryを使った<code>helm</code>コマンドのチュートリアルを飛ばしてください。Railsのサンプルアプリは2GBでも動作します。</p><p>また、今回は初めから<code>Ingress</code>アドオンを有効にしています。minikubeインスタンスを削除した場合は忘れずにもう一度有効にしてください。</p><pre><code># サンプルアプリのコードをチェックアウト$ 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</code></pre><h1 id="Helmとは"><a href="#Helmとは" class="headerlink" title="Helmとは"></a>Helmとは</h1><p><a href="https://helm.sh/" target="_blank" rel="noopener">Helm - The Kubernetes Package Manager</a></p><p>アプリケーションに必要なマニフェストファイル一式をパッケージ化して管理するためのツールです。これを使うと必要なコンテナをまとめてk8sにデプロイすることができます。このパッケージは<code>Chart</code>と呼ばれます。</p><p>HelmではマニフェストファイルをGoの<a href="https://golang.org/pkg/text/template/" target="_blank" rel="noopener">text/template</a>形式でテンプレート化することができ、イメージのタグやレプリカの数など、マニフェストの一部をパラメータ化することができます。これらのパラメータは通常デフォルト値を持ち、Chartパッケージをk8sにデプロイ（インストール）する際に上書きできます。</p><p>Helmでは公式リポジトリで配布されているChartをk8sクラスタにデプロイすることもできるし、自分で開発しているプライベートなアプリのChartを作成して、Helmでk8sにデプロイすることもできます。</p><ul><li><a href="https://docs.helm.sh/" target="_blank" rel="noopener">Helm Docs</a></li></ul><h1 id="Helmをk8sにインストールする方法"><a href="#Helmをk8sにインストールする方法" class="headerlink" title="Helmをk8sにインストールする方法"></a>Helmをk8sにインストールする方法</h1><p>Helmはクライアント・サーバモデルのアプリケーションです。k8sクラスタ側にTillerと呼ばれるサーバモジュールをインストールします。クライアントは<code>helm</code>コマンドです。</p><p>minikubeの場合は下記のコマンドでTillerをインストールできます。</p><pre><code># インストールが完了するまでブロック$ helm init --wait</code></pre><p>Tillerがインストールされているかどうかは下記のコマンドで確認できます。</p><pre><code>$ helm versionClient: &amp;version.Version{SemVer:&quot;v2.9.1&quot;, GitCommit:&quot;20adb27c7c5868466912eebdf6664e7390ebe710&quot;, GitTreeState:&quot;clean&quot;}Server: &amp;version.Version{SemVer:&quot;v2.9.1&quot;, GitCommit:&quot;20adb27c7c5868466912eebdf6664e7390ebe710&quot;, GitTreeState:&quot;clean&quot;}</code></pre><p>アンインストールは下記のコマンドです。</p><pre><code>$ helm reset</code></pre><h1 id="Helmによるパッケージ管理のチュートリアル"><a href="#Helmによるパッケージ管理のチュートリアル" class="headerlink" title="Helmによるパッケージ管理のチュートリアル"></a>Helmによるパッケージ管理のチュートリアル</h1><p>この章では<a href="https://github.com/getsentry/sentry" target="_blank" rel="noopener">Sentry</a> というWebアプリを題材に、Chartを使ってアプリケーションをminikubeにデプロイするデモをします。</p><p>Sentryはアプリケーションのエラー情報を収集してSlackなどに通知するためのサービスです。SentryのWebサービスはPythonで実装されていますが、Ruby用のクライアントもgemとして配布されており、Railsにも簡単に組み込むことができます。</p><ul><li><a href="https://github.com/getsentry/raven-ruby" target="_blank" rel="noopener">Raven is a Ruby client for Sentry</a></li><li><a href="https://docs.sentry.io/clients/ruby/integrations/rails/" target="_blank" rel="noopener">Ruby on Rails – Sentry Documentation</a></li></ul><p><a href="https://sentry.io/welcome/" target="_blank" rel="noopener">SaaS</a>を利用することもできるのですが、<a href="https://hub.docker.com/_/sentry/" target="_blank" rel="noopener">公式のDockerイメージ</a>が配布されており、Dockerを使って自前で運用することもできます。</p><p>Sentryサービスの内部構成はPythonのWebアプリ、バックグラウンドジョブのためのワーカー、PostgreSQLサーバ、Redisサーバとそれなりに複雑です。ただ、Helm Chartを使えば比較的簡単にセットアップすることが可能です。</p><p>この章では<code>helm</code>コマンドのチュートリアルとしてminikube上にSentryをセットアップする手順を示します。</p><a id="more"></a><h2 id="helm-update-repo-リポジトリ情報の更新"><a href="#helm-update-repo-リポジトリ情報の更新" class="headerlink" title="helm update repo: リポジトリ情報の更新"></a>helm update repo: リポジトリ情報の更新</h2><p>公式のChartリポジトリはここです。</p><p><a href="https://github.com/kubernetes/charts" target="_blank" rel="noopener">https://github.com/kubernetes/charts</a></p><p>このリポジトリはstableとincubatorの二つに別れています。</p><p><code>helm</code>コマンドはローカルにこのリポジトリの情報をキャッシュしています。下記のコマンドでこのキャッシュを更新することができます。</p><pre><code>$ helm repo update...Skip local chart repository...Successfully got an update from the &quot;stable&quot; chart repository...Successfully got an update from the &quot;jenkins-x&quot; chart repositoryUpdate Complete. ⎈ Happy Helming!⎈</code></pre><h2 id="helm-search-Chartの検索"><a href="#helm-search-Chartの検索" class="headerlink" title="helm search: Chartの検索"></a>helm search: Chartの検索</h2><p><code>helm search</code>コマンドでstableのChartを検索することができます。</p><pre><code>$ helm search sentryNAME            CHART VERSION   APP VERSION     DESCRIPTIONstable/sentry   0.1.14          8.17            Sentry is a cross-platform crash reporting and ...</code></pre><p>少しややこしいですが、CHART VERSIONはマニフェストやパラメータ定義などこのChart自体のバージョンを示し、APP VERSIONはSentryのバージョンを示しています。</p><p>パラメータなしで実行すると全てのChartを表示します。</p><h2 id="helm-inspect-Chartの詳細情報の表示"><a href="#helm-inspect-Chartの詳細情報の表示" class="headerlink" title="helm inspect: Chartの詳細情報の表示"></a>helm inspect: Chartの詳細情報の表示</h2><p>Chartのメタ情報やパラメータのデフォルト値、READMEなどを表示するコマンドがあります。</p><pre><code>$ helm inspect stable/sentry</code></pre><p>後述する<code>helm install</code>の<code>-f</code>や<code>--set</code>オプションで指定できるパラメータの一覧などを確認することができます。</p><h2 id="helm-install-Chartのインストール（デプロイ）"><a href="#helm-install-Chartのインストール（デプロイ）" class="headerlink" title="helm install: Chartのインストール（デプロイ）"></a>helm install: Chartのインストール（デプロイ）</h2><p>下記のコマンドを実行すると、<code>stable/sentry</code>というChartをminikubeにデプロイします。</p><pre><code>$ helm install stable/sentry \  --name sentry \  --namespace sentry \  --wait \  --timeout 600 \  --version 0.1.14 \  --set service.type=NodePort \  --set ingress.enabled=true \  --set ingress.hostname=$(minikube ip).nip.io \  --set image.tag=8.22 \  --set email.host=&quot;&quot; \  --set sentrySecret=sentry \  --set postgresql.postgresPassword=postgres \  --set redis.redisPassword=redis</code></pre><p>色々オプションを指定していますが、実際のところは<code>helm install stable/sentry</code>だけでも動作します。今回は比較的よく使いそうなオプションをまとめて紹介します。</p><p>はじめに、特に重要な<code>--name</code>オプションについて説明します。k8sにデプロイされたChartのインスタンスは<code>Release</code>と呼ばれ、一意な名前を持ちます(Helmでは同じChartの<code>Release</code>を二つ以上デプロイすることができます)。この名前を<code>--name</code>オプションで指定することができます。この例では<code>sentry</code>という名前をつけていますが、本来は<code>staging</code>や<code>production</code>など環境を示すような名前をつける方が良いかもしれません。</p><p>名前を指定しない場合は<code>happy-panda</code>のように辞書から単語を二つ適当に拾ってきてくっつけたみたいな名前が自動的に与えられます。<code>helm upgrade</code>や<code>helm delete</code>など、特定の<code>Release</code>を対象とするコマンドはこの名前で対象を指定するので、できるだけわかりやすい名前を自分でつけるのがおすすめです。</p><p>その他のオプションの意味は次の通りです。</p><ul><li><code>--namespace</code>オプションでは、このChartのAPIオブジェクトを作成する<code>Namespace</code>を<code>sentry</code>にしています。<code>Namespace</code>については後述します。</li><li><code>--wait</code>オプションをつけると、各オブジェクトの状態が準備完了になるまで待機してからコマンドを終了します。</li><li><code>--timeout</code>オプションは<code>--wait</code>のタイムアウト値(秒)です。筆者の環境では<code>stable/sentry</code>の初期化に5分強かかるので、10分に拡張しています。</li><li><code>--version</code>オプションはインストールするChartのバージョンです。指定しないと最新版がインストールされます。</li><li><code>--set</code>オプションはChartに定義されているパラメータを入力しています。この例では次のパラメータの値をデフォルトから変更しています。<ul><li><code>service.type</code>: <code>Service</code>オブジェクトのタイプです。デフォルトは<code>LoadBalancer</code>なのですが、minikubeが対応していないため<code>NodePort</code>に変更しています。<code>--wait</code>オプションをつけている場合、<code>LoadBalancer</code>のままだと<code>Service</code>オブジェクトの状態が準備完了にならず、コマンドがタイムアウトして失敗します。</li><li><code>ingress.enabled</code>: デフォルトでは<code>Ingress</code>を使わない設定になっていますが、この例では有効にしています。</li><li><code>ingress.hostname</code>: <code>minikube ip</code>とワイルドカードDNSを組み合わせたURLにしています。</li><li><code>image.tag</code>: <code>sentry</code>イメージのタグです。Sentry自体のバージョンを意味します。デフォルトは8.17ですが、新しいバージョンが出ているので8.22に変更しています。</li><li><code>email.host</code>: 招待メールやレポートなどメールを送信するためのSMTPサーバを指定します。空文字を指定するとメール送信機能が無効化されます。</li><li><code>sentrySecret</code>: Sentry内部での暗号処理の鍵です。省略するとランダムな文字列が自動的に割り当てられます。<a id="note2-link" href="#note2">*2</a></li><li><code>postgres.postgresPassword</code>: PostgreSQLサーバのパスワードです。省略するとランダムな文字列が自動的に割り当てられます。<a id="note2-link" href="#note2">*2</a></li><li><code>redis.redisPassword</code>: Redisサーバのパスワードです。省略するとランダムな文字列が自動的に割り当てられます。<a id="note2-link" href="#note2">*2</a></li></ul></li></ul><p><a id="note2" href="#note2-link">*2</a>: これらのパラメータを省略してランダム文字列にした場合、<code>email.host</code>のような別のパラメータを更新した場合にもランダム文字列が再生成されてDBに接続できなくなるなどの問題が起きるため、継続的に運用する予定なら明示的に設定しておくのが無難です。</p><p><code>--set</code>では見ての通り複数のパラメータを指定できます。パラメータの一覧を書いたYAMLファイルを<code>-f</code>オプションに渡すことでも同様のことができます。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># values-custom.yaml</span><span class="token key atrule">service</span><span class="token punctuation">:</span>  <span class="token key atrule">type</span><span class="token punctuation">:</span> NodePort<span class="token key atrule">ingress</span><span class="token punctuation">:</span>  <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span class="token key atrule">image</span><span class="token punctuation">:</span>  <span class="token key atrule">tag</span><span class="token punctuation">:</span> <span class="token number">8.22</span><span class="token key atrule">email</span><span class="token punctuation">:</span>  <span class="token key atrule">host</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token key atrule">sentrySecret</span><span class="token punctuation">:</span> sentry<span class="token key atrule">postgresql</span><span class="token punctuation">:</span>  <span class="token key atrule">postgresPassword</span><span class="token punctuation">:</span> postgres<span class="token key atrule">redis</span><span class="token punctuation">:</span>  <span class="token key atrule">redisPassword</span><span class="token punctuation">:</span> redis<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上記のようなYAMLファイルを用意して、<code>-f</code>オプションに渡します。この例では、<code>ingress.hostname</code>のみ<code>minikube ip</code>コマンドの出力を使いたいので<code>--set</code>で指定しています。</p><pre><code>$ helm install stable/sentry \  --name sentry \  --namespace sentry \  --wait \  --timeout 600 \  --version 0.1.14 \  --f values-custom.yaml \  --set ingress.hostname=$(minikube ip).nip.io</code></pre><p>このChartのソースコードは下記にあります。他にどのようなパラメータがあるかなどはここを参照してください。</p><p><a href="https://github.com/kubernetes/charts/blob/master/stable/sentry" target="_blank" rel="noopener">kubernetes/charts | stable/sentry</a></p><p>結構時間がかかるので、別の端末にログを表示して進捗を確認できるようにしておくと良いでしょう。</p><pre><code>$ stern --namespace sentry &quot;.*&quot;</code></pre><p>完了すると下記のようなメッセージが表示されます。</p><pre><code>NAME:   sentryLAST DEPLOYED: Tue May 22 00:07:12 2018NAMESPACE: sentrySTATUS: DEPLOYEDRESOURCES:==&gt; v1/SecretNAME               TYPE    DATA  AGEsentry-postgresql  Opaque  1     5msentry-redis       Opaque  1     5msentry-sentry      Opaque  3     5m==&gt; v1/PersistentVolumeClaimNAME               STATUS  VOLUME                                    CAPACITY  ACCESS MODES  STORAGECLASS  AGEsentry-postgresql  Bound   pvc-a3af7917-5d08-11e8-9576-9a07dd1ebfb4  8Gi       RWO           standard      5msentry-redis       Bound   pvc-a3b0850d-5d08-11e8-9576-9a07dd1ebfb4  8Gi       RWO           standard      5msentry-sentry      Bound   pvc-a3b159e9-5d08-11e8-9576-9a07dd1ebfb4  10Gi      RWO           standard      5m==&gt; v1/ServiceNAME               TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)         AGEsentry-postgresql  ClusterIP  10.106.179.194  &lt;none&gt;       5432/TCP        5msentry-redis       ClusterIP  10.104.155.56   &lt;none&gt;       6379/TCP        5msentry-sentry      NodePort   10.101.218.50   &lt;none&gt;       9000:31416/TCP  5m==&gt; v1beta1/DeploymentNAME                  DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGEsentry-postgresql     1        1        1           1          5msentry-redis          1        1        1           1          5msentry-sentry-cron    1        1        1           1          5msentry-sentry-web     1        1        1           1          5msentry-sentry-worker  2        2        2           2          5m==&gt; v1beta1/IngressNAME           HOSTS                 ADDRESS        PORTS  AGEsentry-sentry  192.168.64.29.nip.io  192.168.64.29  80     5m==&gt; v1/Pod(related)NAME                                   READY  STATUS   RESTARTS  AGEsentry-postgresql-5795779885-hh88k     1/1    Running  0         5msentry-redis-6f8d889d4d-r5wbh          1/1    Running  0         5msentry-sentry-cron-8578df4d69-2kzd9    1/1    Running  0         5msentry-sentry-web-6b857b74c8-96fsq     1/1    Running  1         5msentry-sentry-worker-66cf9bb6db-2qzkh  1/1    Running  0         5msentry-sentry-worker-66cf9bb6db-x9plg  1/1    Running  0         5mNOTES:1. Get the application URL by running these commands:  export NODE_PORT=$(kubectl get --namespace sentry -o jsonpath=&quot;{.spec.ports[0].nodePort}&quot; services sentry-sentry)  export NODE_IP=$(kubectl get nodes --namespace sentry -o jsonpath=&quot;{.items[0].status.addresses[0].address}&quot;)  echo http://$NODE_IP:$NODE_PORT/auth/login/sentry2. Log in with  USER: admin@sentry.local  Get login password with    kubectl get secret --namespace sentry sentry-sentry -o jsonpath=&quot;{.data.user-password}&quot; | base64 --decode</code></pre><p>作成されたAPIオブジェクトのレポートと、Chart側で設定された初期メッセージが<code>NOTES</code>以下に表示されています。</p><p><code>1. Get the application URL...</code>にはログインURLの取得方法が書いてありますが、これは<code>NodePort</code>を使う場合のものです。今回はパラメータで<code>Ingress</code>を有効にしているので、ログインURLは <code>open http://$(minikube ip).nip.io/</code> でOKです。<code>2. Log in with</code>手順に従って初期ユーザのパスワードを取得し、Sentryの管理画面にログインできることを確認してください。ユーザ名は <code><a href="mailto:admin@sentry.local" target="_blank" rel="noopener">admin@sentry.local</a></code> で、パスワードは上記の<code>kubectl get secret</code>コマンドを実行して表示されるテキストです。最後に<code>%</code>が化けて表示されるようですが、その前までのテキストを入力してください。</p><p>公式のChartはだいたいインストール後にこのようなヘルプメッセージが表示されます。このメッセージもGoのテンプレート構文で記述されています。</p><p><a href="https://github.com/kubernetes/charts/blob/master/stable/sentry/templates/NOTES.txt" target="_blank" rel="noopener">kubernetes/charts | stable/sentry/templates/NOTES.txt</a></p><h2 id="helm-list-クラスタ上のChartインスタンス-Release-の一覧を表示"><a href="#helm-list-クラスタ上のChartインスタンス-Release-の一覧を表示" class="headerlink" title="helm list: クラスタ上のChartインスタンス(Release)の一覧を表示"></a>helm list: クラスタ上のChartインスタンス(Release)の一覧を表示</h2><p>k8sクラスタにインストール済みのReleaseの一覧は下記のコマンドで確認できます。</p><pre><code>$ helm listNAME    REVISION        UPDATED                         STATUS          CHART           NAMESPACEsentry  1               Tue May 22 00:07:12 2018        DEPLOYED        sentry-0.1.14   sentry</code></pre><h2 id="helm-upgrade-Chartの更新のデプロイ"><a href="#helm-upgrade-Chartの更新のデプロイ" class="headerlink" title="helm upgrade: Chartの更新のデプロイ"></a>helm upgrade: Chartの更新のデプロイ</h2><p><code>Release</code>名を指定して、</p><pre><code>$ helm upgrade sentry</code></pre><h2 id="helm-delete-ReleaseとAPIオブジェクトの削除"><a href="#helm-delete-ReleaseとAPIオブジェクトの削除" class="headerlink" title="helm delete: ReleaseとAPIオブジェクトの削除"></a>helm delete: ReleaseとAPIオブジェクトの削除</h2><p>作成したReleaseによって作られたAPIオブジェクトを削除する場合は、下記のコマンドを実行します。</p><pre><code>$ helm delete --purge sentry</code></pre><p>パラメータにはReleaseの名前を指定します。<code>--purge</code>オプションをつけるとRelease自体も削除します。このオプションをつけない場合、APIオブジェクトだけが削除され、Releaseは削除されずにHelmの管理DBに残る点に注意してください。purgeするまでその名前のReleaseを新しく作ることはできません。</p><p>なお、<code>stable/sentry</code>の場合は少し厄介で、<a href="https://github.com/kubernetes/charts/tree/master/stable/sentry#uninstalling-the-chart" target="_blank" rel="noopener">READMEにも書かれている</a>通り<code>helm delete</code>しただけでは<code>Job</code>のオブジェクトが残ります。</p><pre><code>$ kubectl get all --namespace sentryNAME                           READY     STATUS      RESTARTS   AGEpod/sentry-db-init-9mnqq       0/1       Completed   0          22hpod/sentry-db-init-ckgzj       0/1       Error       0          22hpod/sentry-user-create-8dgbj   0/1       Completed   0          22hNAME                           DESIRED   SUCCESSFUL   AGEjob.batch/sentry-db-init       1         1            22hjob.batch/sentry-user-create   1         1            22h</code></pre><p>これらのオブジェクトは下記のように個別に削除する必要があります。</p><pre><code>$ kubectl delete job/sentry-db-init job/sentry-user-create</code></pre><p><code>sentry-db-init</code>の<code>sentry</code>部分は<code>Release</code>の名前です。別の名前で<code>helm install</code>するとここも変わるので注意してください。後述するようにネームスペースごと削除してしまう方が簡単です。</p><pre><code>$ kubectl delete ns sentry</code></pre><h1 id="NamespaceとContextについて"><a href="#NamespaceとContextについて" class="headerlink" title="NamespaceとContextについて"></a>NamespaceとContextについて</h1><p>k8sには<code>Namespace</code>というオブジェクトがあり、<code>Deployment</code>や<code>Service</code>など主要なオブジェクトは基本的にいずれかの<code>Namespace</code>に属しています。前回の記事まで全く<code>Namespace</code>を指定してこなかったのですが、ずっと<code>default</code>という組み込みの<code>Namespace</code>が操作の対象になっていました。</p><pre><code># defaultネームスペースのPodのリストを表示$ kubectl get pods# sentryネームスペースのPodのリストを表示$ kubectl -n sentry get pods</code></pre><p>また、これまでのサンプルでは<code>Service</code>のエンドポイントにアクセスする際にはそのサービスの名前をDNS名として指定しました。同一のネームスペース内のオブジェクトから接続する場合はそれで良いのですが、異なるネームスペースのオブジェクトから接続する場合には<code>${サービス名}.${ネームスペース名}</code>のようにドメインを追加する必要があります。</p><p>ところで、<code>default</code>以外のネームスペースを主に使う場合に、<code>kubectl</code>のパラメータにその都度<code>-n</code>オプションを指定するのは面倒です。</p><p><code>kubectl</code>コマンドには<code>Context</code>という概念があり、コマンド側で接続先のk8sクラスタやデフォルトのネームスペースを記憶しています。</p><ul><li>接続先のクラスタやデフォルトのネームスペースなどの情報を<code>Context</code>という単位で保存</li><li><code>Context</code>は複数保存でき、有効になっているのはそのうちの一つ(<code>CURRENT</code>)</li><li>有効な<code>Context</code>を切り替えることで接続先クラスタやデフォルトのネームスペースを変更可能</li></ul><p>これらの設定は <code>~/.kube/config</code> に記録されています。</p><h2 id="kubectl-config-get-contexts-Contextの一覧の表示"><a href="#kubectl-config-get-contexts-Contextの一覧の表示" class="headerlink" title="kubectl config get-contexts: Contextの一覧の表示"></a>kubectl config get-contexts: Contextの一覧の表示</h2><p>minikubeしか使っていない場合は下記のようになっていると思います。</p><pre><code>$ kubectl config get-contextsCURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE*         minikube   minikube   minikube</code></pre><h2 id="kubectl-config-current-context-現在有効になっているContext名の表示"><a href="#kubectl-config-current-context-現在有効になっているContext名の表示" class="headerlink" title="kubectl config current-context: 現在有効になっているContext名の表示"></a>kubectl config current-context: 現在有効になっているContext名の表示</h2><pre><code>$ kubectl config current-contextminikube</code></pre><h2 id="kubectl-config-view-Contextの詳細の表示"><a href="#kubectl-config-view-Contextの詳細の表示" class="headerlink" title="kubectl config view: Contextの詳細の表示"></a>kubectl config view: Contextの詳細の表示</h2><p><code>~/.kube/config</code>の中身を表示しているだけです。</p><pre><code>$ kubectl config viewapiVersion: v1clusters:- cluster:    certificate-authority: /Users/kawahara_taisuke/.minikube/ca.crt    server: https://192.168.64.29:8443  name: minikubecontexts:- context:    cluster: minikube    user: minikube  name: minikubecurrent-context: minikubekind: Configpreferences: {}users:- name: minikube  user:    client-certificate: /Users/kawahara_taisuke/.minikube/client.crt    client-key: /Users/kawahara_taisuke/.minikube/client.key</code></pre><h2 id="kubectl-config-set-context-Contextの作成または更新"><a href="#kubectl-config-set-context-Contextの作成または更新" class="headerlink" title="kubectl config set-context: Contextの作成または更新"></a>kubectl config set-context: Contextの作成または更新</h2><p>現在の<code>Context</code>のネームスペースを<code>sentry</code>に変更するにはこうします。</p><pre><code>$ kubectl config set-context $(kubectl config current-context) --namespace=sentryContext &quot;minikube&quot; modified.# 変わっていることを確認$ kubectl config get-contextsCURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE*         minikube   minikube   minikube   sentry</code></pre><p>無指定(default)に戻す場合はこうします。</p><pre><code>$ kubectl config unset contexts.minikube.namespaceProperty &quot;contexts.minikube.namespace&quot; unset.# 戻っていることを確認$ kubectl config get-contextsCURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE*         minikube   minikube   minikube</code></pre><p>存在しない名前を指定すると新しい<code>Context</code>を作ります。次の例はクラスタがminikubeでネームスペースがsentryである<code>Context</code>の例です。</p><pre><code>$ kubectl config set-context minikube-sentry --cluster=minikube --user=minikube --namespace=sentry$ kubectl config get-contextsCURRENT   NAME              CLUSTER    AUTHINFO   NAMESPACE*         minikube          minikube   minikube          minikube-sentry   minikube   minikube   sentry</code></pre><h2 id="kubectl-config-use-context-有効なContextを変更"><a href="#kubectl-config-use-context-有効なContextを変更" class="headerlink" title="kubectl config use-context: 有効なContextを変更"></a>kubectl config use-context: 有効なContextを変更</h2><pre><code>$ kubectl config use-context minikube-sentry% kubectl config get-contextsCURRENT   NAME              CLUSTER    AUTHINFO   NAMESPACE          minikube          minikube   minikube*         minikube-sentry   minikube   minikube   sentry</code></pre><h2 id="kubectl-config-delete-context-Contextの削除"><a href="#kubectl-config-delete-context-Contextの削除" class="headerlink" title="kubectl config delete-context: Contextの削除"></a>kubectl config delete-context: Contextの削除</h2><p>指定したコンテキストを削除できます。</p><pre><code>$ kubectl config delete-context minikube-sentry</code></pre><h2 id="kubectxとkubens"><a href="#kubectxとkubens" class="headerlink" title="kubectxとkubens"></a>kubectxとkubens</h2><p><code>Context</code>とネームスペースの切り替えは見ての通り煩雑です。<a href="https://github.com/ahmetb/kubectx" target="_blank" rel="noopener">kubectxとkubens</a>というツールを使えばもっと簡単に切り替えの操作を行うことができます。</p><p>macOSの場合はHomebrewでインストール可能です。</p><pre><code>$ brew install kubectx</code></pre><p>インストール時に<code>--with-short-name</code>オプションをつけると、コマンド名を<code>kctx</code>と<code>kns</code>に変更できます。<code>kubectl</code>との前方一致部分が減るのでシェルでの補完が少し楽になります。</p><p><code>kctx(kubectx)</code>は<code>Context</code>の一覧表示と切り替え、削除、リネームが可能です。</p><pre><code>$ kctx                        # パラメータ無しだとContext一覧を表示(有効なものは黄色で表示)$ kctx minikube-sentry        # 指定したContextに切り替え(Tabで補完が効く)$ kctx -                      # 一つ前のContextに戻る$ kctx -d minikube-sentry     # Contextを削除$ kctx sentry=minikube-sentry # minikube-sentry を sentry にリネーム$ kctx sentry=.               # 現在のContextを sentry にリネーム</code></pre><p><code>kns(kubens)</code>は現在の<code>Context</code>に対して、ネームスペースの一覧表示と切り替えが可能です。</p><pre><code>$ kns         # ネームスペースの一覧表示(現在のネームスペースを黄色で表示)$ kns sentry  # 指定したネームスペースをデフォルトに変更(Tabで補完が効く)$ kns -       # 一つ前のネームスペースに変更</code></pre><p>なお、<code>kubectx(kctx)</code>では<code>Context</code>の作成や削除はできませんので<code>kubectl</code>コマンドを使う必要があります。ただ、知っての通りminikubeの場合は<code>minikube start</code>の際に自動的に<code>Context</code>が作成されますし、GCPのGKEの場合も指定したGKEクラスタに接続するための<code>Context</code>を作成する機能が<code>gcloud</code>コマンドに組み込まれています。</p><p><a href="https://cloud.google.com/sdk/gcloud/reference/container/clusters/get-credentials" target="_blank" rel="noopener">gcloud container clusters get-credentials  |  Cloud SDK  |  Google Cloud</a></p><p>minikubeやGKEを使っている限りでは<code>Context</code>を作成したり編集したりする機会はそれほど多くないと思いますので、<code>kctx</code>と<code>kns</code>の使い方を覚えておけばほとんどの場合十分です。</p><h2 id="Namespaceの削除について"><a href="#Namespaceの削除について" class="headerlink" title="Namespaceの削除について"></a>Namespaceの削除について</h2><p><code>helm install</code>で<code>--namespace</code>オプションを指定すると、当該ネームスペースがない場合は自動的に作成されます。このネームスペースは<code>helm delete</code>しても残ります。</p><p>ネームスペースを削除する場合には<code>kubectl delete</code>を使います。</p><pre><code>$ kubectl delete namespace sentry# または$ kubectl delete ns sentry</code></pre><p><strong>ネームスペース内のオブジェクトが全て削除される点に注意してください。</strong></p><p>永続ボリュームも確認無しで削除されるため、事故によるデータロストに十分注意する必要があります。<code>PV</code>オブジェクトが削除された場合に、データの実体(GCPのPersistentDiskやAWSのEBSなど)が削除されず残るようにするためには、<code>StorageClass</code>の<a href="https://kubernetes.io/docs/concepts/storage/storage-classes/#reclaim-policy" target="_blank" rel="noopener">reclaimPolicy</a>を<code>Retain</code>に変更しておく必要があります。</p><h1 id="Chartの作り方"><a href="#Chartの作り方" class="headerlink" title="Chartの作り方"></a>Chartの作り方</h1><p>前節では既存のパッケージを使う方法を見てきました。今節では自分でパッケージを作り、k8sクラスタにデプロイする方法を見ていきます。</p><p>具体的には、<a href="/blog/2018/05/30/docker-compose-with-rails/#サンプルアプリケーション">第2回で取り上げたサンプルアプリ</a>のHelm Chartを作成します。構成は<a href="/blog/2018/05/30/practical-kubernetes-with-rails/#Step4-Ingress">Step4のマニフェストファイル</a>と同等にします。</p><p>最終的なコードは下記に置いてあります。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/chart/" target="_blank" rel="noopener">k8s/chart/</a></p><p>なお、このドキュメントでは作ったHelm Chartをアプリのパッケージとして公開するというより、プライベートなアプリのk8sマニフェストをHelmで管理できるようにChart化する、という使い方を想定しています。</p><p>このような使い方においては、作成したChartをリポジトリサーバに登録する必要はなく、<code>helm</code>コマンドでローカルFS上のChartディレクトリを直接指定することでChartのデプロイを行うことができます。</p><h2 id="helm-upgrade-–install-作ったChartのデプロイ"><a href="#helm-upgrade-–install-作ったChartのデプロイ" class="headerlink" title="helm upgrade –install: 作ったChartのデプロイ"></a>helm upgrade –install: 作ったChartのデプロイ</h2><p>サンプルコードのChartをデプロイしてみましょう。</p><p>もしもまだであれば、<a href="#準備">準備</a>の節を参照して、サンプルコードのチェックアウトとminikubeの起動を済ませて置いてください。</p><p>次に、サンプルアプリのイメージをminikube上でビルドしておいてください。</p><pre><code>$ cd rails-k8s-demoapp$ (eval $(minikube docker-env) &amp;&amp; $ docker build . -t demoapp:0.0.1)</code></pre><p>また、Step4と同じく<code>Ingress</code>のTLS証明書用の<code>Secret</code>オブジェクトは<code>kubectl</code>コマンドで作成します。証明書と鍵の内容をChartのパラメータ化することも不可能ではないのですが煩雑なので、公式リポジトリのChartも<code>Secret</code>オブジェクトの名前だけをパラメータ化しているものが多いようです。今回はそれにならいます。</p><p>ちょっと手を抜いてMakefileを利用して作成します。手順はStep4の時と同じなので詳細は前回の記事を参照してください。</p><pre><code>cd k8s/chart$ make kubectl-create-secret-tls</code></pre><p>Tiller(Helmのサーバモジュール)のインストールがまだであれば下記を実行します。</p><pre><code>$ helm init --wait</code></pre><p>次に、<code>helm upgrade</code>コマンドでChartをminikubeにデプロイします。</p><pre><code>$ helm upgrade staging . --install --wait \  --set ingress.host=demoapp-puma.$(minikube ip).nip.io</code></pre><p><code>staging</code>は<code>Release</code>の名前です。<code>--install</code>オプションをつけると、<code>Release</code>が存在しない場合のみ<code>helm install</code>のような動作をするようになります。初回と2回目以降でコマンドを分けずに済むので便利です。</p><p><code>--set</code>では、<code>ingress.host</code>というパラメータを指定しています。</p><p>最後にブラウザでWebアプリを開きます。</p><pre><code>$ open https://demoapp-puma.$(minikube ip).nip.io/</code></pre><p>Makefileに同様のタスクを定義しているので、下記のコマンドでも同じことができます。</p><pre><code>$ make helm-init$ make minikube-docker-build$ make kubectl-create-secret-tls$ make helm-upgrade$ make open# または単に$ make</code></pre><p><code>Release</code>と証明書の削除は次のコマンドです。</p><pre><code>make clean</code></pre><h2 id="helm-template-テンプレートのレンダリング"><a href="#helm-template-テンプレートのレンダリング" class="headerlink" title="helm template: テンプレートのレンダリング"></a>helm template: テンプレートのレンダリング</h2><p>Chartに含まれるテンプレートが最終的にどのようなYAMLデータになるかを事前に確認するには、<code>helm template</code>コマンドを使います。</p><pre><code>$ cd rails-k8s-demoapp/k8s/chart$ helm template . --name staging</code></pre><p>上記のコマンドは、すべてのテンプレートにパラメータを埋め込んだ結果を表示します。</p><ul><li><code>.</code>はChartディレクトリのパスです。<code>stable/sentry</code>のようにリポジトリのChartを指定することもできます。</li><li><code>--name</code>オプションは<code>Release</code>の名前です。この他に、<code>--set</code>や<code>-f</code>でパラメータを指定することもできます。レンダリングの際はそれらの値を埋め込んだ結果が表示されます。</li></ul><p>また、特定のテンプレートのみレンダリングしたい場合は<code>-x</code>オプションを指定します。</p><pre><code>$ helm template . --name staging -x templates/puma-deploy.yaml</code></pre><p><code>-x</code>オプションの値は、カレントディレクトリからの相対パスではなく、Chartのルートディレクトリからの相対パスを指定する必要がある点に注意してください。</p><h2 id="helm-create-新しいChartの作成"><a href="#helm-create-新しいChartの作成" class="headerlink" title="helm create: 新しいChartの作成"></a>helm create: 新しいChartの作成</h2><p><code>demoapp</code>という名前のChartを作るには下記のようにします。</p><pre><code>$ cd rails-k8s-demoapp/k8s$ helm create demoapp</code></pre><p>次のような構成のディレクトリが作られます。</p><pre><code>demoapp/ Chart.yaml values.yaml charts/ templates/    NOTES.txt    _helpers.tpl    deployment.yaml    ingress.yaml    service.yaml</code></pre><p>作成後、<code>demoapp</code>ディレクトリはリネームしても問題ありません。</p><ul><li><code>Chart.yaml</code>には、Chartの名前やバージョンなどの情報が記述されます。</li><li><code>values.yaml</code>には、Chartのパラメータのデフォルト値が記述されます。</li><li><code>charts/</code>には、このChartが依存している別のChartを保存します。今回は扱いません。</li><li><code>templates/</code>には、次のファイルを保存します。<ul><li><code>NOTES.txt</code>は、Chartのインストール後に表示するメッセージです。</li><li><code>_helpers.tpl</code>には、テンプレート全般で再利用するための部分テンプレートを書きます。</li><li>残りの<code>*.yaml</code>がマニフェストのテンプレートです。</li></ul></li></ul><p><code>helm create</code>で作成した直後のChartは、<code>Deployment</code>と<code>Service</code>を一つずつ作成して、<code>Ingress</code>経由でそれを外部に公開するというk8sの基本構成が記述されています。なので、それらを一旦削除します。</p><pre><code>rm demoapp/values.yaml demoapp/templates/*.yaml</code></pre><p>次に、Step4のマニフェスト一式を<code>demoapp/templates/</code>以下にコピーします。</p><pre><code>cp manifests-step4/*.yaml demoapp/templates/</code></pre><p>さて、これでおおよそ動くChartができました。<code>Ingress</code>以外はこのままでも動作します <a id="note1-link" href="#note1">*1</a>。<code>demoapp/templates/</code>に置くのはテンプレートなので、テンプレートとしての構文を一切使わないYAMLのままでもとりあえず動くのです。マニフェストファイルが存在するプロジェクトをHelm Chart化する場合には、このように一旦<code>templates/</code>ディレクトリに置いてから各マニフェストファイルをテンプレート構文で書き換えていくことになります。</p><p>そうやって作成したChartが下記のパスにあります。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/chart/" target="_blank" rel="noopener">k8s/chart/</a></p><p>次節以降では、このChartの中のテンプレートとStep4のマニフェストを比較しつつ、Chartの書き方を解説します。</p><p><a id="note1" href="#note1-link">*1</a>: <code>Ingress</code>については、ホスト名を示すパラメータが<code>$(minikube ip)</code>というコマンドで取得するまでわからないため、下記のように元々<code>sed</code>で<code>${MINIKUBE_IP}</code>を書き換えてから<code>kubectl apply -f -</code>に流し込む前提のマニフェストでした。Chart化する際には、テンプレートの機能を使ってパラメータとしてこのホスト名を指定できるようにします。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step4/puma-ing.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> extensions/v1beta1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Ingress<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma  <span class="token comment" spellcheck="true"># 省略</span><span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">tls</span><span class="token punctuation">:</span>  <span class="token punctuation">-</span> <span class="token key atrule">hosts</span><span class="token punctuation">:</span>    <span class="token punctuation">-</span> demoapp<span class="token punctuation">-</span>puma.$<span class="token punctuation">{</span>MINIKUBE_IP<span class="token punctuation">}</span>.nip.io    <span class="token key atrule">secretName</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma<span class="token punctuation">-</span>tls  <span class="token key atrule">rules</span><span class="token punctuation">:</span>  <span class="token punctuation">-</span> <span class="token key atrule">host</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma.$<span class="token punctuation">{</span>MINIKUBE_IP<span class="token punctuation">}</span>.nip.io    <span class="token comment" spellcheck="true"># 省略</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre><code>export MINIKUBE_IP=$(minikube ip)cat *.yaml | sed s/\${MINIKUBE_IP}/$(MINIKUBE_IP)/ | kubectl apply --record -f -</code></pre><p>詳細な説明は <a href="/blog/2018/05/30/practical-kubernetes-with-rails/#Ingress%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88">前回の記事</a>を参照してください。</p><h2 id="テンプレート"><a href="#テンプレート" class="headerlink" title="テンプレート"></a>テンプレート</h2><p>Helm Chartのテンプレート構文は、ほぼ<a href="https://golang.org/pkg/text/template/" target="_blank" rel="noopener">Go言語のtext/template</a>です。ただし、下記のように拡張されています。</p><ul><li><a href="https://godoc.org/github.com/Masterminds/sprig" target="_blank" rel="noopener">Sprig</a>のtemplate functionが50個ほど使えるようになっています。</li><li>ValuesやChart, Releaseなどの<a href="https://docs.helm.sh/chart_template_guide/#built-in-objects" target="_blank" rel="noopener">組み込みオブジェクト</a>が追加されています。</li></ul><p>はじめに、Step4時点での<code>puma</code>の<code>Deployment</code>を例に、Chartのテンプレート化の勘所とテンプレート記法の基本を説明します。</p><p>元になるマニフェストは下記です。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step4/puma-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> puma<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">2</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp      <span class="token key atrule">component</span><span class="token punctuation">:</span> puma  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">labels</span><span class="token punctuation">:</span>        <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp        <span class="token key atrule">component</span><span class="token punctuation">:</span> puma    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> puma          <span class="token key atrule">image</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">:</span>0.0.1          <span class="token key atrule">imagePullPolicy</span><span class="token punctuation">:</span> IfNotPresent          <span class="token key atrule">command</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> ./bin/start<span class="token punctuation">-</span>puma          <span class="token key atrule">livenessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">httpGet</span><span class="token punctuation">:</span>              <span class="token key atrule">path</span><span class="token punctuation">:</span> /health_check/full              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>            <span class="token key atrule">initialDelaySeconds</span><span class="token punctuation">:</span> <span class="token number">30</span>          <span class="token key atrule">readinessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">httpGet</span><span class="token punctuation">:</span>              <span class="token key atrule">path</span><span class="token punctuation">:</span> /health_check/full              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>            <span class="token key atrule">initialDelaySeconds</span><span class="token punctuation">:</span> <span class="token number">30</span>          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>これをテンプレート化したものが下記です。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/chart/templates/puma-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.puma.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span>  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span class="token key atrule">chart</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.chart" . <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span class="token key atrule">release</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Release.Name <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span class="token key atrule">heritage</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Release.Service <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span class="token key atrule">component</span><span class="token punctuation">:</span> puma<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Values.puma.replicas <span class="token punctuation">}</span><span class="token punctuation">}</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span>      <span class="token key atrule">release</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Release.Name <span class="token punctuation">}</span><span class="token punctuation">}</span>      <span class="token key atrule">component</span><span class="token punctuation">:</span> puma  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">labels</span><span class="token punctuation">:</span>        <span class="token key atrule">app</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span>        <span class="token key atrule">release</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Release.Name <span class="token punctuation">}</span><span class="token punctuation">}</span>        <span class="token key atrule">component</span><span class="token punctuation">:</span> puma      <span class="token key atrule">annotations</span><span class="token punctuation">:</span>        <span class="token key atrule">checksum/rails-env-cm</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> include (print $.Template.BasePath "/rails<span class="token punctuation">-</span>env<span class="token punctuation">-</span>cm.yaml") . <span class="token punctuation">|</span> sha256sum <span class="token punctuation">}</span><span class="token punctuation">}</span>        <span class="token key atrule">checksum/rails-env-secret</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> include (print $.Template.BasePath "/rails<span class="token punctuation">-</span>env<span class="token punctuation">-</span>secret.yaml") . <span class="token punctuation">|</span> sha256sum <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> puma          <span class="token key atrule">image</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Values.rails.image.repository <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">:</span><span class="token punctuation">{</span><span class="token punctuation">{</span> .Values.rails.image.tag <span class="token punctuation">}</span><span class="token punctuation">}</span>          <span class="token key atrule">imagePullPolicy</span><span class="token punctuation">:</span> IfNotPresent          <span class="token key atrule">command</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> ./bin/start<span class="token punctuation">-</span>puma          <span class="token key atrule">livenessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">httpGet</span><span class="token punctuation">:</span>              <span class="token key atrule">path</span><span class="token punctuation">:</span> /health_check/full              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>            <span class="token key atrule">initialDelaySeconds</span><span class="token punctuation">:</span> <span class="token number">30</span>          <span class="token key atrule">readinessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">httpGet</span><span class="token punctuation">:</span>              <span class="token key atrule">path</span><span class="token punctuation">:</span> /health_check/full              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>            <span class="token key atrule">initialDelaySeconds</span><span class="token punctuation">:</span> <span class="token number">30</span>          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.rails<span class="token punctuation">-</span>env.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span>            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.rails<span class="token punctuation">-</span>env.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>このようにテンプレートでは<code>&#123;&#123; &#125;&#125;</code>の中にコード片を埋め込んでいきます。</p><p>いっぱい差分があるように見えますが、書き換えられているものは次の四つです。</p><ul><li>レプリカの数やイメージの名前など、パラメータ化されたもの</li><li>オブジェクトの名前</li><li>ラベル</li><li>アノテーション</li></ul><p>次節以降で、一つずつ詳細を見ていきます。</p><h3 id="パラメータ化"><a href="#パラメータ化" class="headerlink" title="パラメータ化"></a>パラメータ化</h3><p>Chart版のマニフェストでは、レプリカの数とイメージの名前がパラメータ化されています。Step4のマニフェストとChartのテンプレートから関連する部分だけを抜き出して比較します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># before: k8s/manifests-step4/puma-deploy.yaml</span><span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">2</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> puma          <span class="token key atrule">image</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">:</span>0.0.1<span class="token comment" spellcheck="true"># after: k8s/chart/templates/puma-deploy.yaml</span><span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Values.puma.replicas <span class="token punctuation">}</span><span class="token punctuation">}</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> puma          <span class="token key atrule">image</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Values.rails.image.repository <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">:</span><span class="token punctuation">{</span><span class="token punctuation">{</span> .Values.rails.image.tag <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>Values</code>という組み込みオブジェクトの値を参照しています。これはChartの利用者側で任意に変更可能なパラメータを管理するオブジェクトです。パラメータのデフォルト値はChartディレクトリの<code>values.yaml</code>ファイルに記述します。この値は<code>helm install</code>または<code>helm upgrade</code>の際に<code>--set</code>オプションや<code>-f</code>オプションで上書きすることができます。(詳細は前半のSentryのデモを参照してください)</p><p>サンプルコードの<code>values.yaml</code>から今回関連のある部分だけを以下に抜き出しています。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">puma</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">2</span><span class="token key atrule">rails</span><span class="token punctuation">:</span>  <span class="token key atrule">image</span><span class="token punctuation">:</span>    <span class="token key atrule">repository</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">tag</span><span class="token punctuation">:</span> 0.0.1<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>このYAMLデータの中身を<code>.Values.puma.replicas</code>のように<code>.</code>区切りで指定することができます。</p><h3 id="オブジェクトの名前"><a href="#オブジェクトの名前" class="headerlink" title="オブジェクトの名前"></a>オブジェクトの名前</h3><p>次に、オブジェクトの名前に関する部分だけを抜き出して比較します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># before: k8s/manifests-step4/puma-deploy.yaml</span><span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> puma          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env<span class="token comment" spellcheck="true"># after: k8s/chart/templates/puma-deploy.yaml</span><span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.puma.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> puma          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.rails<span class="token punctuation">-</span>env.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span>            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.rails<span class="token punctuation">-</span>env.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>template</code>というのは、定義済みの部分テンプレートを呼び出す<code>Action</code>です。Helmの場合、<code>templates/_helpers.tpl</code>で<code>define</code>により定義した値を埋め込むことになります。この例では、<code>demoapp.puma.name</code>という定義を参照しています。</p><p>以下、<code>templates/_helpers.tpl</code>から関連のある部分だけ抜き出します。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token comment" spellcheck="true">/* k8s/chart/templates/_helpers.tpl */</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> define <span class="token string">"demoapp.fullname"</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> <span class="token keyword">if</span> <span class="token punctuation">.</span>Values<span class="token punctuation">.</span>fullnameOverride <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> <span class="token punctuation">.</span>Values<span class="token punctuation">.</span>fullnameOverride <span class="token operator">|</span> trunc <span class="token number">63</span> <span class="token operator">|</span> trimSuffix <span class="token string">"-"</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> <span class="token keyword">else</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> $name <span class="token operator">:=</span> <span class="token keyword">default</span> <span class="token punctuation">.</span>Chart<span class="token punctuation">.</span>Name <span class="token punctuation">.</span>Values<span class="token punctuation">.</span>nameOverride <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> <span class="token keyword">if</span> contains $name <span class="token punctuation">.</span>Release<span class="token punctuation">.</span>Name <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> <span class="token punctuation">.</span>Release<span class="token punctuation">.</span>Name <span class="token operator">|</span> trunc <span class="token number">63</span> <span class="token operator">|</span> trimSuffix <span class="token string">"-"</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> <span class="token keyword">else</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> printf <span class="token string">"%s-%s"</span> <span class="token punctuation">.</span>Release<span class="token punctuation">.</span>Name $name <span class="token operator">|</span> trunc <span class="token number">63</span> <span class="token operator">|</span> trimSuffix <span class="token string">"-"</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> end <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> end <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> end <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> define <span class="token string">"demoapp.puma.name"</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> include <span class="token string">"demoapp.fullname"</span> <span class="token punctuation">.</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">-</span>puma<span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> end <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> define <span class="token string">"demoapp.rails-env.name"</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> include <span class="token string">"demoapp.fullname"</span> <span class="token punctuation">.</span> <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">-</span>rails<span class="token operator">-</span>env<span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">-</span> end <span class="token operator">-</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>demoapp.fullname</code>、<code>demoapp.puma.name</code>, <code>demoapp.rials-env.name</code>の三つの<code>define</code>があります。前者は<code>helm create</code>した時から存在するもので、残り二つは筆者が追加したものです。</p><p>結構ボリュームがあるのですが、ほとんどの状況では次のようなシンプルな結果になります。</p><hr><ul><li><code>demoapp.fullname</code>は、Chart.yamlに書かれた<code>name</code>フィールドの値に<code>Release</code>の名前をプレフィクスとしてつけた文字列を返します。サンプルの場合は、<code>Release</code>の名前が<code>staging</code>なら<code>staging-demoapp</code>になります。</li><li><code>demoapp.puma.name</code>は、<code>Release</code>が<code>staging</code>の場合は<code>staging-demoapp-puma</code>になります。<code>include</code>は、<code>define</code>定義の中でさらに別の部分テンプレートを参照する時に使います。</li><li><code>demoapp.rails-env.name</code>は、<code>Release</code>が<code>staging</code>の場合は<code>staging-demoapp-rails-env</code>になります。</li></ul><hr><p>つまりChart化した後のオブジェクト名には、元々のマニフェストでの名前である<code>demoapp-puma</code>にプレフィクスとして<code>Release</code>の名前をつけていることになります。Helm Chartでは、このようにオブジェクトの名前に<code>Release</code>の名前をプレフィクスとしてつけるのが慣例です。</p><p>なお、<code>define</code>を追加せず<code>demoapp.fullname</code>だけを使って下記のように書くことも可能です。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.fullname" . <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">-</span>puma<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> puma          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.fullname" . <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.fullname" . <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>公式のHelmチャートにはこのように書かれているものも多いですが、typoに気づきにくいという問題があるので、このサンプルでは前記のように<code>define</code>して共通化しています。</p><p>ところで、<code>template</code>関数の呼び出しの際には名前を示す一つ目のパラメータの後ろに<code>.</code>がありますが、これはカレントスコープを渡していることを示しています。テンプレート定義の内部で<code>.Values</code>や<code>.Release</code>などの組み込みオブジェクトを参照する場合には、呼び出し時にスコープを渡す必要がある点に注意してください。これは<code>include</code>も同様です。</p><p><a href="https://docs.helm.sh/chart_template_guide/#setting-the-scope-of-a-template" target="_blank" rel="noopener">SETTING THE SCOPE OF A TEMPLATE</a></p><h3 id="ラベル"><a href="#ラベル" class="headerlink" title="ラベル"></a>ラベル</h3><p>次に、ラベルに関連するところだけを抜き出して比較します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># before: k8s/manifests-step4/puma-deploy.yaml</span><span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> puma<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp      <span class="token key atrule">component</span><span class="token punctuation">:</span> puma<span class="token comment" spellcheck="true"># after: k8s/chart/templates/puma-deploy.yaml</span><span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span class="token key atrule">chart</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.chart" . <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span class="token key atrule">release</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Release.Name <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span class="token key atrule">heritage</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Release.Service <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span class="token key atrule">component</span><span class="token punctuation">:</span> puma<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span>      <span class="token key atrule">release</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Release.Name <span class="token punctuation">}</span><span class="token punctuation">}</span>      <span class="token key atrule">component</span><span class="token punctuation">:</span> puma<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>元々<code>app</code>と<code>component</code>だったところに、<code>chart</code>、<code>release</code>、<code>heritage</code>が追加されています。これらのラベルは、Helmの公式ドキュメントでBest Practiceのページに標準的なラベルとして定義されているものです。</p><p><a href="https://docs.helm.sh/chart_best_practices/#standard-labels" target="_blank" rel="noopener">STANDARD LABELS - Chart Best Practices</a></p><p><code>chart</code>はChartの名前、<code>release</code>は<code>Release</code>の名前を示すラベルです。<code>heritage</code>は常に<code>Tiller</code>という文字列になります。これはこのオブジェクトがHelm(Tiller)によって管理されているということを示します。</p><p><code>release</code>のみ、<code>.spec.selector.matchLabels</code>にも追加されている点に注意してください。これは同一のChartが同じ<code>Namespace</code>内に複数の<code>Release</code>としてデプロイされた場合に、適切に分離されるために必要です。<code>chart</code>と<code>heritage</code>は動作に直接影響しませんが、慣例として推奨されているラベルです。</p><h3 id="アノテーション"><a href="#アノテーション" class="headerlink" title="アノテーション"></a>アノテーション</h3><p>最後にアノテーションについて説明します。</p><p>下記のように、マニフェストにはなかった<code>.spec.template.metadata.annotations</code>エントリがテンプレートには追加されています。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># after: k8s/chart/templates/puma-deploy.yaml</span><span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">annotations</span><span class="token punctuation">:</span>        <span class="token key atrule">checksum/rails-env-cm</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> include (print $.Template.BasePath "/rails<span class="token punctuation">-</span>env<span class="token punctuation">-</span>cm.yaml") . <span class="token punctuation">|</span> sha256sum <span class="token punctuation">}</span><span class="token punctuation">}</span>        <span class="token key atrule">checksum/rails-env-secret</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> include (print $.Template.BasePath "/rails<span class="token punctuation">-</span>env<span class="token punctuation">-</span>secret.yaml") . <span class="token punctuation">|</span> sha256sum <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>アノテーションは、オブジェクトの識別<strong>以外</strong>に使われるメタ情報です(識別に使うのはラベル)。ラベルと同様にKey-Value形式の構造で、通常はビルドやリリースに関する情報やモニタリングのための情報などを記録します。</p><p>ところで、<code>ConfigMap</code>または<code>Secret</code>から環境変数などの設定情報を参照している<code>Deployment</code>には、運用上少し面倒な制限があります。それは、<code>ConfigMap</code>や<code>Secret</code>の内容を更新したとしても、それを参照する<code>Deployment</code>はローリングアップデートされるわけではないということです。</p><p>一方、<code>Deployment</code>はこのアノテーションが更新された場合もローリングリスタートが行われます。そこでそれを利用して、<code>ConfigMap</code>または<code>Secret</code>の更新後に<code>kubectl patch</code>コマンドなどを用いてアノテーションを更新することで強制的にローリングリスタートをかけるというテクニックが知られています。</p><pre><code>kubectl patch deploy demoapp-puma -p \  &quot;{\&quot;spec\&quot;:{\&quot;template\&quot;:{\&quot;metadata\&quot;:{\&quot;annotations\&quot;:{\&quot;config-updated-at\&quot;:\&quot;`date +&#39;%s&#39;`\&quot;}}}}}&quot;</code></pre><p>Helmでテンプレート使う場合には、先に挙げた例のように<code>ConfigMap</code>および<code>Secret</code>のマニフェストのダイジェストをアノテーションに含めることで、これらの設定オブジェクトの内容が変わったときに自動的にローリングリスタートがかかるようにできます。このダイジェスト計算はレンダリング後のマニフェストに対して行われるため、例えば<code>rails-env-cm.yaml</code>に全く変更が無い場合でも、<code>values.yaml</code>を編集するなどして<code>rails-env-cm.yaml</code>内部で参照している<code>Values</code>の値が変われば<code>Deployment</code>はリスタートされます。</p><p><a href="https://docs.helm.sh/charts_tips_and_tricks/#automatically-roll-deployments-when-configmaps-or-secrets-change" target="_blank" rel="noopener">Automatically Roll Deployments When ConfigMaps or Secrets change</a></p><p>同様のアノテーションを<code>sidekiq-deploy.yaml</code>にも追加しています。なお、<code>mysql-deploy.yaml</code>でも<code>ConfigMap</code>と<code>Secret</code>を参照していますが、こちらは初回起動時に作成するDBやユーザなどの情報が主で運用中に変更することを想定していないため、アノテーションは追加していません。</p><h3 id="関数とパイプライン"><a href="#関数とパイプライン" class="headerlink" title="関数とパイプライン"></a>関数とパイプライン</h3><p>関数とパイプラインを使いこなすことでテンプレートはより便利になります。</p><p><code>Secret</code>オブジェクトの定義では、データの値を次に示すようにBASE64エンコードして記述する必要がありました。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step4/rails-env-secret.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Secret<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env<span class="token key atrule">data</span><span class="token punctuation">:</span>  <span class="token key atrule">SECRET_KEY_BASE</span><span class="token punctuation">:</span> MTIz    <span class="token comment" spellcheck="true"># echo -n "123" | base64</span>  <span class="token key atrule">MYSQL_PASSWORD</span><span class="token punctuation">:</span> c2VjcmV0 <span class="token comment" spellcheck="true"># echo -n "secret" | base64</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>いちいちエンコードしてからファイルに記述するのは面倒です。</p><p>Helmのテンプレートでは先に述べたように<a href="https://godoc.org/github.com/Masterminds/sprig" target="_blank" rel="noopener">Sprig</a>の関数が組み込まれているため、<code>b64enc</code>という関数を使って次のように<code>Secret</code>を定義することができます。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/chart/templates/rails-env-secret.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Secret<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> template "demoapp.rails<span class="token punctuation">-</span>env.name" . <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token key atrule">data</span><span class="token punctuation">:</span>  <span class="token key atrule">SECRET_KEY_BASE</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Values.env.secret.SECRET_KEY_BASE <span class="token punctuation">|</span> b64enc <span class="token punctuation">}</span><span class="token punctuation">}</span>  <span class="token key atrule">MYSQL_PASSWORD</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> .Values.env.secret.MYSQL_PASSWORD <span class="token punctuation">|</span> b64enc <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/chart/values.yaml</span><span class="token key atrule">env</span><span class="token punctuation">:</span>  <span class="token key atrule">secret</span><span class="token punctuation">:</span>    <span class="token key atrule">MYSQL_PASSWORD</span><span class="token punctuation">:</span> secret    <span class="token key atrule">SECRET_KEY_BASE</span><span class="token punctuation">:</span> <span class="token string">"123"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>&#123;&#123; &#125;&#125;</code>の中身はパイプラインであり、<code>|</code> 演算子を使って別の関数のパラメータとすることができます。関数が二つ以上のパラメータをとる場合、<code>|</code>の後ろの関数には最後のパラメータとして<code>|</code>の前の結果が渡されます。</p><p>比較的よく使われる関数に<code>default</code>と<code>quote</code>があります。これを使った例を一つ見てみましょう。</p><pre><code>{{ .Values.rails.testValue | default "abc" | b64enc | quote }}</code></pre><p><code>default</code>は、二つ目のパラメータが空の場合は一つ目の値を、空でない場合は二つ目の値を返す関数です。<code>quote</code>は、パラメータの前後に<code>&quot;</code>をつけた文字列を返す関数です。上記の例では、<code>.Values.rails.testValue</code>をBASE64エンコードして<code>&quot;</code>で囲むという処理です。<code>.Values.rails.testValue</code>が空だった場合は<code>abc</code>という文字列を代わりにエンコードしてクォートします。</p><h3 id="フロー制御"><a href="#フロー制御" class="headerlink" title="フロー制御"></a>フロー制御</h3><p>今回のサンプルでは特に使っていないのですが、テンプレートでは<code>if/else</code>による分岐や<code>range</code>によるループを使用できます。</p><p><a href="https://docs.helm.sh/chart_template_guide/#flow-control" target="_blank" rel="noopener">Flow Control</a></p><p><code>stable/sentry</code>の<code>ingress.yaml</code>が参考になると思います。</p><p><a href="https://github.com/kubernetes/charts/blob/master/stable/sentry/templates/ingress.yaml" target="_blank" rel="noopener">https://github.com/kubernetes/charts/blob/master/stable/sentry/templates/ingress.yaml</a></p><h2 id="values-yaml"><a href="#values-yaml" class="headerlink" title="values.yaml"></a>values.yaml</h2><p>ここでは<code>values.yaml</code>について説明します。サンプルコードの<code>values.yaml</code>は次のような内容です。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">mysql</span><span class="token punctuation">:</span>  <span class="token key atrule">image</span><span class="token punctuation">:</span>    <span class="token key atrule">repository</span><span class="token punctuation">:</span> mysql    <span class="token key atrule">tag</span><span class="token punctuation">:</span> 5.7.21  <span class="token key atrule">storage</span><span class="token punctuation">:</span>    <span class="token key atrule">size</span><span class="token punctuation">:</span> 8Gi    <span class="token key atrule">className</span><span class="token punctuation">:</span> standard<span class="token key atrule">redis</span><span class="token punctuation">:</span>  <span class="token key atrule">image</span><span class="token punctuation">:</span>    <span class="token key atrule">repository</span><span class="token punctuation">:</span> redis    <span class="token key atrule">tag</span><span class="token punctuation">:</span> 4.0.9  <span class="token key atrule">storage</span><span class="token punctuation">:</span>    <span class="token key atrule">size</span><span class="token punctuation">:</span> 8Gi    <span class="token key atrule">className</span><span class="token punctuation">:</span> standard<span class="token key atrule">puma</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">2</span><span class="token key atrule">sidekiq</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">1</span>  <span class="token key atrule">timeout</span><span class="token punctuation">:</span> <span class="token number">60</span><span class="token key atrule">rails</span><span class="token punctuation">:</span>  <span class="token key atrule">image</span><span class="token punctuation">:</span>    <span class="token key atrule">repository</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">tag</span><span class="token punctuation">:</span> 0.0.1    <span class="token key atrule">setupDbTag</span><span class="token punctuation">:</span> 0.0.1<span class="token key atrule">ingress</span><span class="token punctuation">:</span>  <span class="token key atrule">tlsSecretName</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma<span class="token punctuation">-</span>tls  <span class="token comment" spellcheck="true"># required from --set parameter</span>  <span class="token comment" spellcheck="true"># helm upgrade release-name . --set ingress.host=demoapp-puma.${MINIKUBE_IP}.nip.io</span>  <span class="token comment" spellcheck="true"># host: demoapp-puma.${MINIKUBE_IP}.nip.io</span><span class="token key atrule">env</span><span class="token punctuation">:</span>  <span class="token key atrule">configmap</span><span class="token punctuation">:</span>    <span class="token key atrule">MYSQL_USER</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">MYSQL_DATABASE</span><span class="token punctuation">:</span> demoapp_production    <span class="token key atrule">RAILS_SERVE_STATIC_FILES</span><span class="token punctuation">:</span> <span class="token string">"true"</span>    <span class="token key atrule">RAILS_LOG_TO_STDOUT</span><span class="token punctuation">:</span> <span class="token string">"true"</span>  <span class="token key atrule">secret</span><span class="token punctuation">:</span>    <span class="token key atrule">MYSQL_PASSWORD</span><span class="token punctuation">:</span> secret    <span class="token key atrule">MYSQL_ROOT_PASSWORD</span><span class="token punctuation">:</span> topsecret    <span class="token key atrule">SECRET_KEY_BASE</span><span class="token punctuation">:</span> <span class="token string">"123"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注意が必要なのは下記の点です。</p><ul><li><code>Job</code>オブジェクトで参照するイメージのタグは<code>Deployment</code>と分ける必要があります。具体的には<code>.rails.image.tag</code>と<code>.rails.image.setupDbTag</code>です。<code>Job</code>のイメージは変更不可なので、分けておかないとアプリを更新できなくなります。</li><li>秘匿情報は集約しておく方が管理が楽です。この例では<code>.env.secret</code>の下にまとめています。こうしておくと<a href="https://github.com/mozilla/sops#extract-a-sub-part-of-a-document-tree" target="_blank" rel="noopener">sops</a>や<a href="https://github.com/joker1007/yaml_vault" target="_blank" rel="noopener">yaml_vault</a>でファイルの一部だけ暗号化するのが簡単になります。これについては次節で詳細を説明します。</li></ul><p>参考:</p><ul><li><a href="https://docs.helm.sh/chart_template_guide/#values-files" target="_blank" rel="noopener">Values Files</a></li><li><a href="https://docs.helm.sh/chart_best_practices/#values" target="_blank" rel="noopener">Values - Best Practices</a></li></ul><h2 id="values-yamlに含まれる秘密情報の暗号化"><a href="#values-yamlに含まれる秘密情報の暗号化" class="headerlink" title="values.yamlに含まれる秘密情報の暗号化"></a>values.yamlに含まれる秘密情報の暗号化</h2><p>先の例では、DBのパスワードやRailsの<code>SECRET_KEY_BASE</code>などの秘密情報が<code>values.yaml</code>に平文で書き込まれていました。プライベートなアプリの場合でも、こういった秘密情報は平文のままでリポジトリにコミットするべきではありません。</p><p>ここでは、YAMLファイルを暗号化する方法を二つ紹介します。</p><h3 id="GCP-KMSの準備"><a href="#GCP-KMSの準備" class="headerlink" title="GCP KMSの準備"></a>GCP KMSの準備</h3><p>紹介する方法は、いずれもGCP KMSまたはAWS KMSで暗号鍵を管理できます。今回はGCP KMSを使う方法を紹介します。</p><p><a href="https://cloud.google.com/sdk/" target="_blank" rel="noopener">Cloud SDK</a>をインストールした後、適当なプロジェクトを作成しください。その後、下記のコマンドを実行して鍵を作成してください。</p><pre><code># ログイン$ gcloud auth login# キーリングの作成$ gcloud kms keyrings create demoapp --location global# キーの作成$ gcloud kms keys create values-key --location global --keyring demoapp --purpose encryption# 作成したキーの確認$ gcloud kms keys list --location global --keyring demoappNAME                                                                                PURPOSE          LABELS  PRIMARY_ID  PRIMARY_STATEprojects/rails-k8s-demoapp/locations/global/keyRings/demoapp/cryptoKeys/values-key  ENCRYPT_DECRYPT          1           ENABLED</code></pre><p>この例では、プロジェクトIDは<code>rails-k8s-demoapp</code>です。各自の環境に合わせて読み替えてください。</p><p>途中、下記のようなメッセージが出た場合は、ブラウザでリンクを開いてKMSを有効化してください。</p><pre><code>ERROR: (gcloud.kms.keyrings.create) FAILED_PRECONDITION: Google Cloud KMS API has not been used in this project before, or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudkms.googleapis.com/overview?project=123456789012 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.</code></pre><p>使い終わった鍵は忘れずに破棄しておいてください。使っていなくても費用が発生します。</p><p><a href="https://cloud.google.com/kms/pricing?hl=ja" target="_blank" rel="noopener">https://cloud.google.com/kms/pricing?hl=ja</a></p><p>また、下記のページを参照してサービスアカウントキーを作成してください。</p><p><a href="https://cloud.google.com/docs/authentication/getting-started" target="_blank" rel="noopener">https://cloud.google.com/docs/authentication/getting-started</a></p><p>役割は、<code>Cloud KMS - クラウドKMS暗号鍵の暗号化/復号化</code>を選択します。JSON形式でダウンロードした鍵を <code>kms-key.json</code>という名前で保存してください。</p><h3 id="yaml-vault"><a href="#yaml-vault" class="headerlink" title="yaml_vault"></a>yaml_vault</h3><p><a href="https://github.com/joker1007/yaml_vault" target="_blank" rel="noopener">yaml_vault</a>はruby製の暗号化ツールです。AWS KMSまたはGCP KMSで暗号鍵を管理することができます。</p><p>gemでインストールできます。GCP KMSを使う場合は、google-api-clientも必要です。</p><pre><code>$ gem install yaml_vault google-api-client</code></pre><p>Gemfileに追加しても良いですが、そのままだとDockerイメージのサイズが50MBほど増えるため、少し工夫が必要です。</p><p>下記のコマンドを実行すると、<code>values.yaml</code>を暗号化して<code>encrypted_values.yaml</code>に出力します。その際、暗号化するキーを<code>--key</code>オプションで限定している点に注目してください。このオプションを指定しない場合、YAMLの全ての値が暗号化されます。</p><pre><code>$ yaml_vault encrypt values.yaml -o encrypted_values.yaml --cryptor=gcp-kms \  --gcp-kms-resource-id=projects/rails-k8s-demoapp/locations/global/keyRings/demoapp/cryptoKeys/values-key \  --gcp-credential-file=kms-key.json \  --key &#39;$.env.secret&#39;encrypted values.yaml -&gt; encrypted_values.yaml</code></pre><p>暗号化されたファイルは次のような内容になります。</p><pre><code># encrypted_values.yamlmysql:  image:    repository: mysql    tag: 5.7.21  storage:    size: 8Gi    className: standardredis:  image:    repository: redis    tag: 4.0.9  storage:    size: 8Gi    className: standardpuma:  replicas: 2sidekiq:  replicas: 1  timeout: 60rails:  image:    repository: demoapp    tag: 0.0.1    setupDbTag: 0.0.1ingress:  tlsSecretName: demoapp-puma-tlsenv:  configmap:    MYSQL_USER: demoapp    MYSQL_DATABASE: demoapp_production    RAILS_SERVE_STATIC_FILES: &quot;true&quot;    RAILS_LOG_TO_STDOUT: &quot;true&quot;  secret:    MYSQL_PASSWORD: CiQA/TkaHXJd4HsdRnMTJ9tgso7tYdCbIJxuWbGgj5UJfpkbI+gSOADQcvdxFdICOfR8oiQa1GOM9UX0koe0AC9TM5C+fpV1nRuaajJGAF0IfFRf9KtKKHyz/Qp4ad3E    MYSQL_ROOT_PASSWORD: CiQA/TkaHbi65E4QSHRKwpxRQcD43rMwo4rr9aUgAj8ezPjgtGcSOwDQcvdxaMRAJ+yE87zeGDIBCbeZVzyZMXAg83Odxj2y9fbL4NELzViD5bqx8XoxWe0Y7nhWYJomNpmC    SECRET_KEY_BASE: &quot;CiQA/TkaHfsk/7i7IrlJVhheCPifd0bCvho9CJgRbiR10lnqc1oSMgDQcvdxijXncXs0gYuMfk9/IdeSiL8fApLEDd6zISmgnmZh3d7KxNioeliHZcWApaai&quot;</code></pre><p>復号するときは次のようにします。</p><pre><code>$ yaml_vault decrypt encrypted_values.yaml -o values.yaml --cryptor=gcp-kms \  --gcp-kms-resource-id=projects/rails-k8s-demoapp/locations/global/keyRings/demoapp/cryptoKeys/values-key \  --gcp-credential-file=kms-key.json \  --key &#39;$.env.secret&#39;</code></pre><p>yaml_vaultを使って運用する場合には、<code>values.yaml</code>はコミットせず、暗号文の<code>encrypted_values.yaml</code>をコミットして運用します。</p><p>なお、暗号化を行う際には、元の平文に変更がなかったとしても毎回暗号文の部分は値が変わってしまう点に注意が必要です。</p><h3 id="sops"><a href="#sops" class="headerlink" title="sops"></a>sops</h3><p><a href="https://github.com/mozilla/sops" target="_blank" rel="noopener">sops</a>はmozilla製の暗号化ツールです。</p><p>使用するためには、まずGCPの鍵を環境変数で指定する必要があります。</p><pre><code>$ export GOOGLE_APPLICATION_CREDENTIALS=kms-key.json</code></pre><p>暗号化は下記のように行います。</p><pre><code>$ sops --encrypt --gcp-kms projects/rails-k8s-demoapp/locations/global/keyRings/demoapp/cryptoKeys/values-key values.yaml &gt; encrypted_values.yaml</code></pre><p>ファイル全体が下記のように暗号化されます。</p><pre><code>mysql:    image:        repository: ENC[AES256_GCM,data:eD0o4pw=,iv:3DbUfobOeqAL0kowEKVqbMjtEC88J9/nMysGdMf4nqk=,tag:wEwatUbf9v1/LVPLYkunPA==,type:str]        tag: ENC[AES256_GCM,data:PgWECj+7,iv:UlUPjWbxO+lW6bAm/2F5JTfAbbWa3/wCjyOIyGmEG7I=,tag:vPmfEGQkKKxE7nsJsZfYZA==,type:str]    storage:        size: ENC[AES256_GCM,data:Zn9a,iv:OaFk2rRTiNB3doRZBo5SvL/7j5G1PLKA0H7mBXpa/Ew=,tag:04A9OIJgYlXWOwk35kPHqg==,type:str]        className: ENC[AES256_GCM,data:k4J42uu3fiE=,iv:YrXlk57NgadQg9VQaTbTjo8E5nT5XWZXz6UvNXiSO6E=,tag:hCOuXuQBY3rSxzsY4sx+0A==,type:str]redis:    image:        repository: ENC[AES256_GCM,data:wjqwX64=,iv:hqxguyQImpZFgGz83CyQhfi/tGZbIefTHbpXMiMuL3w=,tag:Y5kdJOxdH4R2HMrYeFv5xA==,type:str]        tag: ENC[AES256_GCM,data:tCM+YHQ=,iv:owoFufImNd81KqP5XVvEdA+3ZHpZMYp1Q56pHm7lW/o=,tag:VuiJh+zPegQvjfMMySRWTA==,type:str]    storage:        size: ENC[AES256_GCM,data:g5n2,iv:AVbPlOkfrBFq9UzuILE6fgt6Xww6311KbyFhXHnpiAk=,tag:S+xMDWCQ3XlT7PCQlB4kiw==,type:str]        className: ENC[AES256_GCM,data:yrqW0RSihnc=,iv:O9wpDqHd3zYNdoS8VJI1y2giLnSByaGAvGrDMrF8ba8=,tag:hmE475vdibBfUc8R21AMzg==,type:str]puma:    replicas: ENC[AES256_GCM,data:oA==,iv:RkUoYXEivf5vzDrWBD7kWaGtyr1k4lApn1T0qWA67K0=,tag:Z4fPAiGEtfuxxaqQM2nXQw==,type:int]sidekiq:    replicas: ENC[AES256_GCM,data:QQ==,iv:NWZPcXVz7tcuXIRftmacJ2SegYfDgh/iwS82ovlYeNA=,tag:J2WSY3vjOkJcNvIJrLFy7w==,type:int]    timeout: ENC[AES256_GCM,data:G5s=,iv:t/866qY7jTnGSRUQAhgXWEw9lcdtHll8fYIQsyARo4A=,tag:yZ2cIFXdJO2xis4B9mzWSA==,type:int]rails:    image:        repository: ENC[AES256_GCM,data:x4dh5BigFg==,iv:C7jm5zl6rr7PeLwgX2i73Yo1yKWp+lv3eA0zCJipuk4=,tag:2FhxjAmnnIYVTsN6WkFoBQ==,type:str]        tag: ENC[AES256_GCM,data:WaWw06E=,iv:S8Tx8rQPfzfk00of3HiNJVJYM3qeNQKNxxyHYyvTAFM=,tag:74upYPpg3fMoEya9YFTVog==,type:str]        setupDbTag: ENC[AES256_GCM,data:zFo1gJ0=,iv:+P0F1hH5TagxLLe7yqCMAfoLtEsu+s5lLV7WKDJFp7s=,tag:T/Bix+hd1DZBIhA634mVrQ==,type:str]ingress:    tlsSecretName: ENC[AES256_GCM,data:sJcZXlktHsdfsYm6BbkGQg==,iv:J+bmgm2qC0wIMSDzXec9xsujOl3kjfFVUv+qmk3FWhs=,tag:qyecdT2dDvsPI+rAGe9+oA==,type:str]    #ENC[AES256_GCM,data:H0TCFT5qkWAfK6E+j9sSjudYmCVAKckB7CQxgwbH,iv:kl625mCSUA/NNV35CU+xez9GOFggKnDm5nXYJ9waVMU=,tag:AKdSsCJNcfdC+I3nvXhStw==,type:comment]    #ENC[AES256_GCM,data:cGNTwdT+KF0CoUCmNVwLxyvUqNozAZvz0xDWzoEB/qElfEMVRSqdPXMjMjsjEy1Apm0WOBfp5HFgZUFW16Q+vucAhgeFAn0mM7WhADNjKZGZUw==,iv:sMn+N9nHPbWB0Raps0559jxHKDMeo98CfnUIjIvTFjU=,tag:bR8mdcFEf2ZaJ2IdKHp9xg==,type:comment]    #ENC[AES256_GCM,data:cl97jwOSlNZmfBAK+GjtzUZgqyABB9bXUN0J+xeFQpc5I3V3Ez2Olrc=,iv:jcrqo86rp+SVSB+lKLee/YdEz+8IfnULzYA+A0vj+SA=,tag:tqSfgPe//SlU0GJSog0PcQ==,type:comment]env:    configmap:        MYSQL_USER: ENC[AES256_GCM,data:ckkEsrePZg==,iv:ssnCPFBqnihIe5c9T79EJlA2vGWsihOI53RL1SwdklA=,tag:ewyXNB6S6Kd25FIcSGtF4w==,type:str]        MYSQL_DATABASE: ENC[AES256_GCM,data:LmgD+B0MF2jaHXiLAbL57UdT,iv:0eivT7Dkkso4zcvbekb0Li5CBGEn4TcaAFzcC7QtvTs=,tag:/k2xCsM+BKkPF540QZNRqA==,type:str]        RAILS_SERVE_STATIC_FILES: ENC[AES256_GCM,data:B7YMSg==,iv:XM6zzCChuIPUUYmOog/XvbaC88j+AZtNcfps2/e/2h0=,tag:AK9aHDYW2WaBTITLQuBA6Q==,type:str]        RAILS_LOG_TO_STDOUT: ENC[AES256_GCM,data:HV9B/Q==,iv:mSKjUGQSmCXKcCutSmjnenb3lVzVHWEKrTa+N+nBexY=,tag:4bYviLxwkVCZgjjNZ3cvFQ==,type:str]    secret:        MYSQL_PASSWORD: ENC[AES256_GCM,data:aRQsAyGK,iv:tjhn1oDqbp83vNartgIL4S8SgveNLUL91gjUzSCBuNo=,tag:1PtcSk8gt0XBuboZzE93pQ==,type:str]        MYSQL_ROOT_PASSWORD: ENC[AES256_GCM,data:QgUKdkPZ3URM,iv:iQl52u3fruHtoBh6uLCJ0ArzvLHbw7XbNVQcKuBbaVs=,tag:u/V3k0D15Ne0TQLfRcTF/g==,type:str]        SECRET_KEY_BASE: ENC[AES256_GCM,data:0Bsu,iv:1MfHBuEa2fF84H+IJXHeufBWH+dJWGEE4zJqts3EJrw=,tag:VAgJgAS+xlgAKnPeff1eHA==,type:str]sops:    kms: []    gcp_kms:    -   resource_id: projects/rails-k8s-demoapp/locations/global/keyRings/demoapp/cryptoKeys/values-key        created_at: &#39;2018-05-28T14:55:44Z&#39;        enc: CiQA/TkaHVv9JRDpzqLYXfGD8QDW92/4xSZhMcuao5VlStYzBAwSSQDQcvdxZjXWoG1h8nEbj9o1TNWvdSlz5rxY/l9WEd/WieUiORJLVxI+MCsZ764vUksSk9jrmPxnLqlrfKy/Klz/6yKGi3pyx/g=    lastmodified: &#39;2018-05-28T14:55:45Z&#39;    mac: ENC[AES256_GCM,data:zoHHamGXApQLsCJSxYuuVkYFo8xAROv+ier9wZYEpn2jDu9vwDZObwEb52Ewx4UTEJphq4KWi/DKodcUZHfyhWIEjqpwwUxqlIXlWmq1AO5XERa/XS0egi6gK3VnKYHsxbRSKF/hY/DOS6bO5A6Fnh0lBfENUZ8qStt5gKtwPOE=,iv:FK4V/TOHJ6cY3mPk4rlrgrSWm8s+HFazY+62d8Cck58=,tag:HkgWUGc4O4rcHZ1jDY+viQ==,type:str]    pgp: []    unencrypted_suffix: _unencrypted    version: 3.0.5</code></pre><p>このファイルを編集するときは、下記のように直接<code>sops</code>コマンドで指定します。</p><pre><code>$ sops encrypted_values.yaml</code></pre><p>するとファイルの内容が復号され、環境変数EDITORで指定したエディタ上で編集可能になります。保存してエディタを閉じると再び暗号化された状態で保存されます。</p><pre><code>$ sops --decrypt --gcp-kms projects/rails-k8s-demoapp/locations/global/keyRings/demoapp/cryptoKeys/values-key encrypted_values.yaml &gt; values.yaml</code></pre><p>sopsもyaml_vaultのようにファイルの一部分だけを暗号化することもできますが、「YAMLデータのキーにサフィックスをつけて指定した部分だけを平文にする」という仕組みであり、<code>values.yaml</code>の構造に直接影響を与える方法なので使いにくいです。</p><p><a href="https://github.com/mozilla/sops#encrypting-only-parts-of-a-file" target="_blank" rel="noopener">Encrypting only part of a file</a></p><p>sopsを使う場合、<code>values.yaml</code>には秘密情報を以外の値だけを書くようにし、<code>secret-values.yaml</code>に秘密情報を全て取り出して、常に<code>-f</code>オプションで<code>secret-values.yaml</code>も指定するという方法が良いと思います。</p><h2 id="Subchart"><a href="#Subchart" class="headerlink" title="Subchart"></a>Subchart</h2><p>Helm Chartでは、他のChartをSubchartとして使用することができます。</p><p><a href="https://docs.helm.sh/chart_template_guide/#subcharts-and-global-values" target="_blank" rel="noopener">Subcharts and Global Values</a></p><p>例えば<code>stable/sentry</code>パッケージは<code>stable/postgres</code>と<code>stable/redis</code>をSubchartとして使用しており、PostgreSQLとRedisのセットアップはこれらのChartに依存しています。</p><p>今回、<code>rails-k8s-demoapp</code>のChartではあえて<code>stable/mysql</code>と<code>stable/redis</code>を使わずに自前でテンプレートを用意しました。この理由は公式のChartと言えどまだ枯れているとは言えないと判断したためです。</p><p>例えば<code>stable/redis</code>ではパスワードを示すパラメータの名前が途中で変わっており、<code>stable/sentry</code>の現時点での最新版(0.1.14)は古いバージョンの<code>stable/redis</code>(0.10.1)に依存しています。そのため最新の<code>stable/redis</code>のドキュメントを見て設定してしまうと動きません。(バージョンを指定してドキュメントを見られるようなサービスもありません)</p><p>また、当然<code>Values</code>で変更できる部分以外の動作は変更できなくなるため、Chart側で十分な柔軟性が確保されていない場合には、自分のアプリケーションの要件を満たせないあるいは途中で満たせなくなる可能性があります。</p><p>このあたりは<code>Chef</code>の(特に初期の)コミュニティクックブックに状況が似ていると思います。</p><p>公式のChartとして公開することを目指すのであればできるだけ他のChartを利用するべきだと思いますが、そうでない場合には無理に依存を増やす必要はありません。公式のChartは汎用性を高めるために多機能なものが多く、全貌を把握すること自体がコストになるケースもあります。簡単なものなら自作した方がトータルでは楽なことも多いです。</p><p>また、Subchartまで完全に自分でコントロール可能な場合はChartの分割を検討しても良いと思いますが、多くのChartに依存されたChartは更新が難しくなるので、そこはトレードオフになります。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>この節では、プライベートなアプリのHelm Chartの作り方に関して、k8sのプレーンなマニフェストをどのようにテンプレート化するかについて、<code>puma-deploy.yaml</code>を例に説明しました。</p><p>その他のオブジェクトのマニフェストについてもだいたい同じような手順でテンプレート化できるため、それらについての詳細な説明は割愛します。コードを見てみてください。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/chart/" target="_blank" rel="noopener">k8s/chart/</a></p><h1 id="自作のChartによるワークフロー"><a href="#自作のChartによるワークフロー" class="headerlink" title="自作のChartによるワークフロー"></a>自作のChartによるワークフロー</h1><p>以下についてはすでに説明しました。</p><ul><li><a href="#helm-create-%E6%96%B0%E3%81%97%E3%81%84Chart%E3%81%AE%E4%BD%9C%E6%88%90">helm create: 新しいChartの作成</a></li><li><a href="#helm-template-%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%AE%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0">helm template: テンプレートのレンダリング</a></li><li><a href="#helm-upgrade-%E2%80%93install-%E4%BD%9C%E3%81%A3%E3%81%9FChart%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4">helm upgrade –install: 作ったChartのデプロイ</a></li></ul><p>この節では、アプリケーションを運用する上でさらに必要になるであろういくつかのオペレーションを説明します。</p><h2 id="helm-upgrade-Chartの更新を反映"><a href="#helm-upgrade-Chartの更新を反映" class="headerlink" title="helm upgrade: Chartの更新を反映"></a>helm upgrade: Chartの更新を反映</h2><p>コマンド自体は初回デプロイと同じ<code>helm upgrade</code>を使いますが、アプリケーションを更新する場合には次のいずれかの方法で<code>Deployment</code>のイメージを変更する必要があります。</p><ol><li><code>--set</code>オプションで<code>rails.image.tag</code>を変更する。</li></ol><p>この場合、次のように新しいタグを指定して実行します。</p><pre><code>$ helm upgrade staging . --install --wait \  --set ingress.host=demoapp-puma.$(minikube ip).nip.io \  --set rails.image.tag=0.0.2</code></pre><ol start="2"><li><code>values.yaml</code>ファイルの<code>.rails.image.tag</code>と<code>Chart.yaml</code>のバージョン情報を書き換えて<code>helm upgrade</code>を実行する。</li></ol><p>差分は典型的には下記のようになります。</p><pre class="line-numbers language-diff"><code class="language-diff">diff --git a/k8s/chart/Chart.yaml b/k8s/chart/Chart.yamlindex 43907c6..a6283b0 100644<span class="token coord">--- a/k8s/chart/Chart.yaml</span><span class="token coord">+++ b/k8s/chart/Chart.yaml</span><span class="token coord">@@ -1,5 +1,5 @@</span> apiVersion: v1<span class="token deleted">-appVersion: "0.0.1"</span><span class="token inserted">+appVersion: "0.0.2"</span> description: A Helm chart for Kubernetes name: demoapp<span class="token deleted">-version: 0.0.1</span><span class="token inserted">+version: 0.0.2</span>diff --git a/k8s/chart/values.yaml b/k8s/chart/values.yamlindex 3f6eea1..96610f8 100644<span class="token coord">--- a/k8s/chart/values.yaml</span><span class="token coord">+++ b/k8s/chart/values.yaml</span>@@ -24,7 +24,7 @@ sidekiq: rails:   image:     repository: demoapp<span class="token deleted">-    tag: 0.0.1</span><span class="token inserted">+    tag: 0.0.2</span>     setupDbTag: 0.0.1 ingress:<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>ファイルを書き換えたら、初回デプロイと全く同じコマンドを実行します。</p><pre><code>$ helm upgrade staging . --install --wait \  --set ingress.host=demoapp-puma.$(minikube ip).nip.io</code></pre><p>簡単なのは1の方法ですが、現在デプロイされているイメージの情報がgit側に残らないというデメリットがあります。</p><p>長期的には2の方法がおすすめです。<code>Chart.yaml</code>の<code>version</code>も更新しておくと、後に紹介する<code>helm history</code>とgitのログでインフラ構成の履歴を追うのが楽になります。</p><p><a href="https://docs.helm.sh/helm/#helm-upgrade" target="_blank" rel="noopener">helm upgrade</a></p><h2 id="helm-history-Releaseの履歴を表示"><a href="#helm-history-Releaseの履歴を表示" class="headerlink" title="helm history: Releaseの履歴を表示"></a>helm history: Releaseの履歴を表示</h2><p>指定した<code>Release</code>の更新履歴を表示します。</p><pre><code>$ helm history stagingREVISION        UPDATED                         STATUS          CHART           DESCRIPTION1               Mon May 28 00:01:37 2018        SUPERSEDED      demoapp-0.0.1   Install complete2               Mon May 28 00:07:00 2018        DEPLOYED        demoapp-0.0.2   Upgrade complete</code></pre><p><code>REVISION</code>は、その<code>Release</code>の現在のバージョンを示す数値で、1から始まり<code>helm upgrade</code>するたびに1ずつ増えていきます。</p><p><a href="https://docs.helm.sh/helm/#helm-history" target="_blank" rel="noopener">helm history</a></p><h2 id="helm-rollback-Releaseを前のリビジョンに戻す"><a href="#helm-rollback-Releaseを前のリビジョンに戻す" class="headerlink" title="helm rollback: Releaseを前のリビジョンに戻す"></a>helm rollback: Releaseを前のリビジョンに戻す</h2><p>Releaseの状態を指定したリビジョンに戻します。</p><pre><code>$ helm rollback staging 1Rollback was a success! Happy Helming!$ helm history stagingREVISION        UPDATED                         STATUS          CHART           DESCRIPTION1               Mon May 28 00:01:37 2018        SUPERSEDED      demoapp-0.0.1   Install complete2               Mon May 28 00:07:00 2018        SUPERSEDED      demoapp-0.0.2   Upgrade complete3               Mon May 28 00:10:31 2018        DEPLOYED        demoapp-0.0.1   Rollback to 1</code></pre><p><a href="https://docs.helm.sh/helm/#helm-rollback" target="_blank" rel="noopener">helm rollback</a></p><h2 id="helm-get-Releaseの詳細を表示"><a href="#helm-get-Releaseの詳細を表示" class="headerlink" title="helm get: Releaseの詳細を表示"></a>helm get: Releaseの詳細を表示</h2><p>指定した<code>Release</code>に関する下記の情報を表示することができます。</p><ul><li>Chartの名称やデプロイ日時などの基本情報</li><li><code>values.yaml</code>と<code>--set</code>や<code>-f</code>オプションなどの値をマージした最終的な<code>Values</code>オブジェクト</li><li>全テンプレートのレンダリング済みマニフェスト</li></ul><pre><code>$ helm get stagingREVISION: 1RELEASED: Mon May 28 00:01:37 2018CHART: demoapp-0.0.1USER-SUPPLIED VALUES:ingress:  host: demoapp-puma.192.168.64.29.nip.ioCOMPUTED VALUES:env:  configmap:    MYSQL_DATABASE: demoapp_production    MYSQL_USER: demoapp    RAILS_LOG_TO_STDOUT: &quot;true&quot;    RAILS_SERVE_STATIC_FILES: &quot;true&quot;(省略)HOOKS:MANIFEST:---# Source: demoapp/templates/mysql-env-secret.yamlapiVersion: v1kind: Secretmetadata:  name: staging-demoapp-mysql-envdata:  MYSQL_PASSWORD: c2VjcmV0  MYSQL_ROOT_PASSWORD: dG9wc2VjcmV0(省略)</code></pre><p><a href="https://docs.helm.sh/helm/#helm-get" target="_blank" rel="noopener">helm get</a></p><h2 id="helm-diff-Chartの差分を表示"><a href="#helm-diff-Chartの差分を表示" class="headerlink" title="helm diff: Chartの差分を表示"></a>helm diff: Chartの差分を表示</h2><p><code>helm</code>コマンドはプラグイン機構により拡張することが可能です。</p><p>利用可能なプラグインは下記のページに掲載されています。</p><p><a href="https://docs.helm.sh/related/#helm-plugins" target="_blank" rel="noopener">HELM PLUGINS</a></p><p>ここではプラグインの一つ<a href="https://github.com/databus23/helm-diff" target="_blank" rel="noopener">Helm Diff</a>を紹介します。</p><p><code>helm plugin</code>コマンドでインストールできます。</p><pre><code>$ helm plugin install https://github.com/databus23/helm-diff</code></pre><p><code>helm diff</code>コマンドを使うと、<code>helm upgrade</code>により発生する差分を<code>diff</code>形式で表示することができます。</p><pre><code>% helm diff upgrade staging . --set ingress.host=demoapp-puma.$(minikube ip).nip.io --set rails.image.tag=0.0.2staging-demoapp-sidekiq, Deployment (apps/v1) has changed:  # Source: demoapp/templates/sidekiq-deploy.yaml  apiVersion: apps/v1  kind: Deployment(省略)-           image: demoapp:0.0.1+           image: demoapp:0.0.2(省略)staging-demoapp-puma, Deployment (apps/v1) has changed:  # Source: demoapp/templates/puma-deploy.yaml  apiVersion: apps/v1  kind: Deployment(省略)-           image: demoapp:0.0.1+           image: demoapp:0.0.2(省略)</code></pre><p>また、<code>helm rollback</code>で発生する差分や、過去のリビジョン同士の差分を表示することもできます。詳細は<a href="https://github.com/databus23/helm-diff" target="_blank" rel="noopener">Helm Diff</a>のドキュメントを参照してください。</p><pre><code>$ helm diff -h</code></pre><h1 id="おわりに"><a href="#おわりに" class="headerlink" title="おわりに"></a>おわりに</h1><p>Railsアプリ開発のためのDocker/Kubernetes入門を主題として、Docker/Kubernetesの基本と、小さなRailsアプリケーションをHelmでk8sクラスタにデプロイする方法について書きました。</p><p>Docker/Kubernetesを本番サービスへ投入するのであれば、さらに下記について学ぶと良いと思います。</p><ul><li>GCPやAWSのようなパブリッククラウドでk8sを使う方法(GKEやkops)</li><li>ロールベースのアクセス制御(<a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/" target="_blank" rel="noopener">Using RBAC Authorization</a>)</li><li>k8sクラスタ自体やその上で稼働するアプリケーションのリソース監視(Prometheusとの統合など)</li><li>Dockerイメージのさらなるスリム化の手法</li><li><code>Pod</code>をどのノードに配置するかを制御する方法(<a href="https://kubernetes.io/docs/concepts/configuration/assign-pod-node/" target="_blank" rel="noopener">Assigning Pods to Nodes</a>、<a href="https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/" target="_blank" rel="noopener">Taints and Tolerations</a>)</li><li><code>Pod</code>に割り当てるCPUやメモリなどの計算資源を制御する方法(<a href="https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/" target="_blank" rel="noopener">Managing Compute Resources for Containers</a>)</li><li>負荷に応じて動的に<code>Pod</code>の数を増減させる方法(<a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/" target="_blank" rel="noopener">Horizontal Pod Autoscaler</a>)</li></ul><p>クラウドを使うならGKEが簡単でおすすめですが、GKEでHelmを使おうとするとRBACが最初のハードルになると思います。次回はその辺りについて書きたいと思います。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。
この記事はシリーズ連載記事の第六回です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一回 &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/&quot;&gt;Docker編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第二回 &lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第三回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-tutorial/&quot;&gt;Kubernetes入門編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第四回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-with-rails/&quot;&gt;Kubernetes基礎編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第五回 &lt;a href=&quot;/blog/2018/05/30/practical-kubernetes-with-rails/&quot;&gt;Kubernetes応用編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第六回 &lt;a href=&quot;/blog/2018/05/30/helm-with-rails/&quot;&gt;Helm編&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;はじめにHelmについて簡単に紹介した後、
前回の &lt;a href=&quot;/blog/2018/05/30/practical-kubernetes-with-rails/&quot;&gt;Kubernetes応用編&lt;/a&gt;
で作成したマニフェストをもとにHelmチャートの作り方を説明します。&lt;/p&gt;
&lt;p&gt;サンプルコードは全て下記のリポジトリにあります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kwhrtsk/rails-k8s-demoapp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kwhrtsk/rails-k8s-demoapp&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;前提&quot;&gt;&lt;a href=&quot;#前提&quot; class=&quot;headerlink&quot; title=&quot;前提&quot;&gt;&lt;/a&gt;前提&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;macOSでの作業を前提としています。&lt;/li&gt;
&lt;li&gt;使用したツールのバージョンなどは &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/#前提&quot;&gt;初回&lt;/a&gt; の記事を参照してください。&lt;/li&gt;
&lt;li&gt;ツールのインストール手順は &lt;a href=&quot;/blog/2018/05/30/kubernetes-tutorial/#開発環境の構築&quot;&gt;第三回&lt;/a&gt; の記事を参照してください。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;準備&quot;&gt;&lt;a href=&quot;#準備&quot; class=&quot;headerlink&quot; title=&quot;準備&quot;&gt;&lt;/a&gt;準備&lt;/h1&gt;&lt;p&gt;まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。
前回とほぼ同じですが、minikubeのメモリを3GBに増やしています。
これは2GBだと後述するSentryの起動に失敗するためです。
すでに2GBで作っている場合はminikubeインスタンスを作り直すか、
Sentryを使った&lt;code&gt;helm&lt;/code&gt;コマンドのチュートリアルを飛ばしてください。
Railsのサンプルアプリは2GBでも動作します。&lt;/p&gt;
&lt;p&gt;また、今回は初めから&lt;code&gt;Ingress&lt;/code&gt;アドオンを有効にしています。
minikubeインスタンスを削除した場合は忘れずにもう一度有効にしてください。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# サンプルアプリのコードをチェックアウト
$ 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
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;Helmとは&quot;&gt;&lt;a href=&quot;#Helmとは&quot; class=&quot;headerlink&quot; title=&quot;Helmとは&quot;&gt;&lt;/a&gt;Helmとは&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;https://helm.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Helm - The Kubernetes Package Manager&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;アプリケーションに必要なマニフェストファイル一式をパッケージ化して管理するためのツールです。
これを使うと必要なコンテナをまとめてk8sにデプロイすることができます。
このパッケージは&lt;code&gt;Chart&lt;/code&gt;と呼ばれます。&lt;/p&gt;
&lt;p&gt;HelmではマニフェストファイルをGoの&lt;a href=&quot;https://golang.org/pkg/text/template/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;text/template&lt;/a&gt;形式でテンプレート化することができ、
イメージのタグやレプリカの数など、マニフェストの一部をパラメータ化することができます。
これらのパラメータは通常デフォルト値を持ち、Chartパッケージをk8sにデプロイ（インストール）する際に上書きできます。&lt;/p&gt;
&lt;p&gt;Helmでは公式リポジトリで配布されているChartをk8sクラスタにデプロイすることもできるし、
自分で開発しているプライベートなアプリのChartを作成して、Helmでk8sにデプロイすることもできます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.helm.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Helm Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;Helmをk8sにインストールする方法&quot;&gt;&lt;a href=&quot;#Helmをk8sにインストールする方法&quot; class=&quot;headerlink&quot; title=&quot;Helmをk8sにインストールする方法&quot;&gt;&lt;/a&gt;Helmをk8sにインストールする方法&lt;/h1&gt;&lt;p&gt;Helmはクライアント・サーバモデルのアプリケーションです。
k8sクラスタ側にTillerと呼ばれるサーバモジュールをインストールします。
クライアントは&lt;code&gt;helm&lt;/code&gt;コマンドです。&lt;/p&gt;
&lt;p&gt;minikubeの場合は下記のコマンドでTillerをインストールできます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# インストールが完了するまでブロック
$ helm init --wait
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tillerがインストールされているかどうかは下記のコマンドで確認できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ helm version
Client: &amp;amp;version.Version{SemVer:&amp;quot;v2.9.1&amp;quot;, GitCommit:&amp;quot;20adb27c7c5868466912eebdf6664e7390ebe710&amp;quot;, GitTreeState:&amp;quot;clean&amp;quot;}
Server: &amp;amp;version.Version{SemVer:&amp;quot;v2.9.1&amp;quot;, GitCommit:&amp;quot;20adb27c7c5868466912eebdf6664e7390ebe710&amp;quot;, GitTreeState:&amp;quot;clean&amp;quot;}

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;アンインストールは下記のコマンドです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ helm reset
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;Helmによるパッケージ管理のチュートリアル&quot;&gt;&lt;a href=&quot;#Helmによるパッケージ管理のチュートリアル&quot; class=&quot;headerlink&quot; title=&quot;Helmによるパッケージ管理のチュートリアル&quot;&gt;&lt;/a&gt;Helmによるパッケージ管理のチュートリアル&lt;/h1&gt;&lt;p&gt;この章では&lt;a href=&quot;https://github.com/getsentry/sentry&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Sentry&lt;/a&gt; というWebアプリを題材に、
Chartを使ってアプリケーションをminikubeにデプロイするデモをします。&lt;/p&gt;
&lt;p&gt;Sentryはアプリケーションのエラー情報を収集してSlackなどに通知するためのサービスです。
SentryのWebサービスはPythonで実装されていますが、Ruby用のクライアントもgemとして配布されており、Railsにも簡単に組み込むことができます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/getsentry/raven-ruby&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Raven is a Ruby client for Sentry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sentry.io/clients/ruby/integrations/rails/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Ruby on Rails – Sentry Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://sentry.io/welcome/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SaaS&lt;/a&gt;を利用することもできるのですが、
&lt;a href=&quot;https://hub.docker.com/_/sentry/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;公式のDockerイメージ&lt;/a&gt;が配布されており、
Dockerを使って自前で運用することもできます。&lt;/p&gt;
&lt;p&gt;Sentryサービスの内部構成はPythonのWebアプリ、バックグラウンドジョブのためのワーカー、PostgreSQLサーバ、Redisサーバとそれなりに複雑です。
ただ、Helm Chartを使えば比較的簡単にセットアップすることが可能です。&lt;/p&gt;
&lt;p&gt;この章では&lt;code&gt;helm&lt;/code&gt;コマンドのチュートリアルとしてminikube上にSentryをセットアップする手順を示します。&lt;/p&gt;
    
    </summary>
    
      <category term="HowTo" scheme="https://chopschips.net/categories/HowTo/"/>
    
    
      <category term="rails" scheme="https://chopschips.net/tags/rails/"/>
    
      <category term="docker" scheme="https://chopschips.net/tags/docker/"/>
    
      <category term="kubernetes" scheme="https://chopschips.net/tags/kubernetes/"/>
    
      <category term="helm" scheme="https://chopschips.net/tags/helm/"/>
    
  </entry>
  
  <entry>
    <title>Railsアプリ開発のためのDocker/Kubernetes入門5 Kubernetes応用編</title>
    <link href="https://chopschips.net/blog/2018/05/30/practical-kubernetes-with-rails/"/>
    <id>https://chopschips.net/blog/2018/05/30/practical-kubernetes-with-rails/</id>
    <published>2018-05-30T13:40:00.000Z</published>
    <updated>2018-05-30T16:13:58.324Z</updated>
    
    <content type="html"><![CDATA[<p>RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。この記事はシリーズ連載記事の第五回です。</p><ul><li>第一回 <a href="/blog/2018/05/30/docker-with-rails/">Docker編</a></li><li>第二回 <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a></li><li>第三回 <a href="/blog/2018/05/30/kubernetes-tutorial/">Kubernetes入門編</a></li><li>第四回 <a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes基礎編</a></li><li>第五回 <a href="/blog/2018/05/30/practical-kubernetes-with-rails/">Kubernetes応用編</a></li><li>第六回 <a href="/blog/2018/05/30/helm-with-rails/">Helm編</a></li></ul><p>前回の<a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes基礎編</a>では、Step1として第2回<a href="/blog/2018/05/30/docker-compose-with-rails/#docker-composeコマンドによるローカルプレビュー環境">Docker Compose/Dockerfile編</a>の<code>docker-compose-preview.yml</code>に相当する構成を<code>Deployment</code>, <code>Service</code>, <code>ConfigMap</code>, <code>Secret</code>の4種のAPIオブジェクトで記述しました。マニフェストファイル一式はサンプルコードの <a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step1/" target="_blank" rel="noopener">k8s/manifests-step1/</a> ディレクトリにあります。</p><p>この構成には下記に挙げる三つの制約があります。</p><ul><li>pumaコンテナを複数起動すると<code>rails db:setup</code>が並列実行されてエラーになる。</li><li>MySQLやRedisのデータが永続化されていないため、コンテナを停止するとデータも消える。</li><li>pumaに外部からアクセスするためのエンドポイントを本番環境で運用するのが難しい。</li></ul><p>今回はこれらの制約をStep2からStep4で解消していきます。</p><p>サンプルコードは全て下記のリポジトリにあります。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp" target="_blank" rel="noopener">https://github.com/kwhrtsk/rails-k8s-demoapp</a></p><h1 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h1><ul><li>macOSでの作業を前提としています。</li><li>使用したツールのバージョンなどは <a href="/blog/2018/05/30/docker-with-rails/#前提">初回</a> の記事を参照してください。</li><li>ツールのインストール手順は <a href="/blog/2018/05/30/kubernetes-tutorial/#開発環境の構築">第三回</a> の記事を参照してください。</li></ul><h1 id="準備"><a href="#準備" class="headerlink" title="準備"></a>準備</h1><p>まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。(前回と同じなのですでにやっている人は読み飛ばしてください)</p><pre><code># サンプルアプリのコードをチェックアウト$ 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</code></pre><h1 id="Step2-Job"><a href="#Step2-Job" class="headerlink" title="Step2: Job"></a>Step2: Job</h1><p>サンプルコードは全て下記のディレクトリにあります。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step2/" target="_blank" rel="noopener">k8s/manifests-step2/</a></p><p>Step1の構成ではpumaのレプリカの数を1に指定していましたが、一定以上の性能を出そうとするとレプリカの数は2以上に設定する必要があります。ところが、このままの構成でpumaコンテナを複数起動すると、<code>rails db:setup</code>が並列実行されてエラーが発生します。</p><p>次のような手順でこれを確認できます。</p><a id="more"></a><p>まず<code>puma-deploy.yaml</code>を書き換えてreplicasを2にします。</p><pre class="line-numbers language-diff"><code class="language-diff">diff --git a/k8s/manifests-step1/puma-deploy.yaml b/k8s/manifests-step1/puma-deploy.yamlindex 003f484..c17f9bb 100644<span class="token coord">--- a/k8s/manifests-step1/puma-deploy.yaml</span><span class="token coord">+++ b/k8s/manifests-step1/puma-deploy.yaml</span>@@ -7,7 +7,7 @@ metadata:     app: demoapp     component: puma spec:<span class="token deleted">-  replicas: 1</span><span class="token inserted">+  replicas: 2</span>   selector:     matchLabels:       app: demoapp<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>次に<code>stern</code>でログを監視しつつ、別の端末で<code>kubectl apply</code>を実行し、APIオブジェクトを作成します。</p><pre><code>$ cd k8s/manifests-step1# デプロイ済みのオブジェクトがある場合は削除$ cat *.yaml | kubectl delete -f -# ログを監視$ stern &quot;demoapp.*&quot;# 別の端末で実行$ cat *.yaml | kubectl apply -f -</code></pre><p>タイミング依存なのでエラーが発生しない場合もありますが、それなりの確率で下記のようなエラーがsternの端末上に表示されるはずです。</p><pre><code>demoapp-puma-749c456c87-2wk5c puma + ./bin/wait-for demoapp-redis 6379demoapp-puma-749c456c87-2wk5c puma + ./bin/rails db:setup_if_not_yetdemoapp-puma-749c456c87-lwwb7 puma + ./bin/wait-for demoapp-redis 6379demoapp-puma-749c456c87-lwwb7 puma + ./bin/rails db:setup_if_not_yetdemoapp-puma-749c456c87-lwwb7 puma Database &#39;demoapp_production&#39; already existsdemoapp-puma-749c456c87-2wk5c puma Database &#39;demoapp_production&#39; already existsdemoapp-puma-749c456c87-lwwb7 puma -- create_table(&quot;messages&quot;, {:options=&gt;&quot;ENGINE=InnoDB DEFAULT CHARSET=utf8&quot;, :force=&gt;:cascade})demoapp-puma-749c456c87-2wk5c puma -- create_table(&quot;messages&quot;, {:options=&gt;&quot;ENGINE=InnoDB DEFAULT CHARSET=utf8&quot;, :force=&gt;:cascade})demoapp-puma-749c456c87-lwwb7 puma    -&gt; 0.0505sdemoapp-puma-749c456c87-2wk5c puma    -&gt; 0.0925sdemoapp-puma-749c456c87-2wk5c puma rails aborted!demoapp-puma-749c456c87-2wk5c puma ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry &#39;20180411125010&#39; for key &#39;PRIMARY&#39;: INSERT INTO `schema_migrations` (version) VALUES (20180411125010)</code></pre><p>2つのpuma用のPodがほぼ同じタイミングで<code>./bin/rails db:setup_if_not_yet</code>を実行していることがわかります。このRakeタスクは第二回 <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a>で説明しましたが、下記のような内容です。</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token comment" spellcheck="true"># lib/tasks/db.rake</span>namespace <span class="token symbol">:db</span> <span class="token keyword">do</span>  desc <span class="token string">"Invoke db:setup task only if the task has never been invoked yet."</span>  task setup_if_not_yet<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token symbol">:environment</span><span class="token punctuation">]</span> <span class="token keyword">do</span>    <span class="token keyword">begin</span>      <span class="token keyword">if</span> <span class="token operator">!</span><span class="token constant">ActiveRecord</span><span class="token punctuation">:</span><span class="token symbol">:SchemaMigration</span><span class="token punctuation">.</span>table_exists<span class="token operator">?</span>        <span class="token comment" spellcheck="true"># データベースは存在するが、必ずあるはずのテーブルがない</span>        <span class="token constant">Rake</span><span class="token punctuation">:</span><span class="token symbol">:Task</span><span class="token punctuation">[</span><span class="token string">"db:setup"</span><span class="token punctuation">]</span><span class="token punctuation">.</span>invoke        exit <span class="token number">0</span>      <span class="token keyword">end</span>    <span class="token keyword">rescue</span> <span class="token constant">ActiveRecord</span><span class="token punctuation">:</span><span class="token symbol">:NoDatabaseError</span>      <span class="token comment" spellcheck="true"># データベースが存在しない</span>      <span class="token constant">Rake</span><span class="token punctuation">:</span><span class="token symbol">:Task</span><span class="token punctuation">[</span><span class="token string">"db:setup"</span><span class="token punctuation">]</span><span class="token punctuation">.</span>invoke      exit <span class="token number">0</span>    <span class="token keyword">end</span>  <span class="token keyword">end</span><span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>rails db:setup</code>の初回実行時に作成されるテーブルの有無を確認して、テーブルが存在しない場合のみ<code>rails db:setup</code>を実行していますが、テーブルの確認とタスクの実行の間がクリティカルセクションになっているため、何らかの方法で排他制御しなければ<code>rails db:setup</code>が2重に実行される可能性があります。前述のエラーはまさにこれが原因です。</p><p>MySQLやRedisを使ってロックを取るなどこれを排他制御することは技術的には可能なのですが、<code>rails db:setup</code>のように一度きりしか実行しないような処理を記述するには <code>Job</code> というAPIオブジェクトが適任ですので、これを例に使い方を説明します。</p><p>ところで、典型的なRailsのワークフローには、DBの初期化を行う<code>rails db:setup</code>に加えて、スキーマの変更などを行うマイグレーション用のタスク<code>rails db:migrate</code>があります。一見するとこれも<code>Job</code>で管理するのが良さそうなのですが、<code>Job</code>は一度APIオブジェクトを作成すると<code>Deployment</code>のようにイメージを変更できないという制約があります。そのため、新しいマイグレーションを作成するたびに<code>Job</code>オブジェクトも新規に作成する必要があり、運用が煩雑になります。また、<code>rails db:setup</code>とは異なり、<code>rails db:migrate</code>には(少なくともDBがMySQLかPostgreSQLの場合には)RDBMSのロック機構を使ってマイグレーションが多重実行されないように排他制御する仕組みが組み込まれているため、<code>puma</code>の起動前に毎回実行しても実質的な問題が発生しません。</p><p>そこで Step2 では全体の構成を下記のように変更します。</p><ul><li><code>rails db:setup</code>は<code>Job</code>として定義し、アプリを最初にk8sにデプロイした時に一度だけ実行する。</li><li><code>rails db:migrate</code>は<code>puma</code>の起動前に毎回実行する。(複数の<code>puma</code>コンテナにより同時に起動される可能性があるが問題ない)</li></ul><p>まずは <code>rails db:setup</code> を実行するための <code>Job</code> のマニフェストを書きます。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step2/setup-db-job.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> batch/v1 <span class="token comment" spellcheck="true"># Deploymentのapps/v1とは異なる点に注意</span><span class="token key atrule">kind</span><span class="token punctuation">:</span> Job<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>setup<span class="token punctuation">-</span>db<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">backoffLimit</span><span class="token punctuation">:</span> <span class="token number">4 </span><span class="token comment" spellcheck="true"># ジョブを失敗したと見なすまでの再試行回数</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">labels</span><span class="token punctuation">:</span>        <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp        <span class="token key atrule">component</span><span class="token punctuation">:</span> setup<span class="token punctuation">-</span>db    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Never <span class="token comment" spellcheck="true"># OnFailureにするとbackoffLimitが効かない場合があるのでNeverにする</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> setup<span class="token punctuation">-</span>db          <span class="token key atrule">image</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">:</span>0.0.1          <span class="token key atrule">command</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> ./bin/setup<span class="token punctuation">-</span>db          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>内容は<code>puma</code>や<code>sidekiq</code>の<code>Deployment</code>と似ています。まず同じように<code>demoapp</code>イメージを指定します。また、<code>.spec.template</code>以下の内容もほぼ同じですが、<code>livenessProbe</code>などの項目が無い分<code>Deployment</code>よりもシンプルな内容になります。</p><p><code>Job</code>固有の設定値について簡単に補足します。</p><ul><li><code>backoffLimit</code>のデフォルト値は6です。4を指定しているのは単にサンプルとして例示するためで深い意味はありません。</li><li><code>restartPolicy</code>は<code>OnFailure</code>か<code>Never</code>を指定できます。<code>OnFailure</code>の場合はジョブが失敗したときに<code>Pod</code>はそのままで内部のコンテナだけ作り直します。<code>Never</code>の場合は<code>Pod</code>ごと作り直します。今回の場合どちらでも良いのですが、<a href="https://github.com/kubernetes/kubernetes/issues/54870" target="_blank" rel="noopener">既知の問題</a>により<code>Never</code>だと<code>backoffLimit</code>が無視されるケースがあると公式ドキュメントにも書いてあるので<code>Never</code>を指定しています。</li></ul><p>詳細は下記のドキュメントを参照してください。</p><ul><li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/" target="_blank" rel="noopener">Jobs - Run to Completion | Kubernetes</a></li><li><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#job-v1-batch" target="_blank" rel="noopener">Kubernetes API Reference Docs</a></li></ul><p><code>command</code>に指定している <code>./bin/setup-db</code> は下記のような内容で、単に<code>mysql</code>の起動を待ち受けてから(Step1では<code>puma</code>で実行していた)<code>rails db:setup_if_not_yet</code>を実行します。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token shebang important">#!/bin/bash</span> -xu<span class="token function">cd</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">dirname</span> $0<span class="token variable">)</span></span>/<span class="token punctuation">..</span>./bin/wait-for <span class="token variable">$MYSQL_HOST</span> 3306./bin/rails db:setup_if_not_yet<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>また、<code>puma-deploy.yaml</code>も次のように修正します。</p><pre class="line-numbers language-diff"><code class="language-diff"><span class="token coord">--- k8s/manifests-step1/puma-deploy.yaml        2018-05-11 23:30:49.000000000 +0900</span><span class="token coord">+++ k8s/manifests-step2/puma-deploy.yaml        2018-05-11 23:57:17.000000000 +0900</span><span class="token coord">@@ -7,7 +7,7 @@</span>     app: demoapp     component: puma spec:<span class="token deleted">-  replicas: 1</span><span class="token inserted">+  replicas: 2</span>   selector:     matchLabels:       app: demoapp<span class="token coord">@@ -24,7 +24,7 @@</span>           image: demoapp:0.0.1           imagePullPolicy: IfNotPresent           command:<span class="token deleted">-            - ./bin/setup-db-and-start-puma</span><span class="token inserted">+            - ./bin/start-puma</span>           livenessProbe:             httpGet:               path: /health_check/full<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>replicas</code>を2に増やし、<code>command</code>を<code>./bin/start-puma</code>に変更しています。<code>./bin/start-puma</code>は下記のような内容です。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token shebang important">#!/bin/bash</span> -x<span class="token function">cd</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">dirname</span> $0<span class="token variable">)</span></span>/<span class="token punctuation">..</span><span class="token function">trap</span> <span class="token string">"pkill -P $$"</span> EXIT./bin/wait-for <span class="token variable">$MYSQL_HOST</span> 3306./bin/wait-for <span class="token variable">$REDIS_HOST</span> 6379./bin/rails db:try_migrate./bin/pumactl start<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Step1では<code>pumactl</code>コマンドの実行前に<code>./bin/rails db:setup_if_not_yet</code>を実行していましたが、代わりに<code>rails db:try_migrate</code>を実行します。このRakeタスクは下記のような定義です。</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token comment" spellcheck="true"># lib/tasks/db.rake</span>namespace <span class="token symbol">:db</span> <span class="token keyword">do</span>  desc <span class="token string">"Wait for db:setup task to complete."</span>  task wait_for_setup_completion<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token symbol">:environment</span><span class="token punctuation">]</span> <span class="token keyword">do</span>    loop <span class="token keyword">do</span>      <span class="token keyword">begin</span>        <span class="token keyword">if</span> <span class="token operator">!</span><span class="token constant">ActiveRecord</span><span class="token punctuation">:</span><span class="token symbol">:InternalMetadata</span><span class="token punctuation">.</span>table_exists<span class="token operator">?</span>          <span class="token comment" spellcheck="true"># データベースは存在するが、必ずあるはずのテーブルがない</span>          sleep <span class="token number">1</span>        <span class="token keyword">elsif</span> <span class="token operator">!</span><span class="token constant">ActiveRecord</span><span class="token punctuation">:</span><span class="token symbol">:InternalMetadata</span><span class="token punctuation">.</span><span class="token function">where</span><span class="token punctuation">(</span>key<span class="token punctuation">:</span> <span class="token symbol">:environment</span><span class="token punctuation">)</span><span class="token punctuation">.</span>exists<span class="token operator">?</span>          <span class="token comment" spellcheck="true"># テーブルはあるがレコードが存在しない</span>          sleep <span class="token number">1</span>        <span class="token keyword">else</span>          <span class="token keyword">break</span>        <span class="token keyword">end</span>      <span class="token keyword">rescue</span> <span class="token constant">ActiveRecord</span><span class="token punctuation">:</span><span class="token symbol">:NoDatabaseError</span>        <span class="token comment" spellcheck="true"># データベースが存在しない</span>        sleep <span class="token number">1</span>      <span class="token keyword">end</span>    <span class="token keyword">end</span>  <span class="token keyword">end</span>  desc <span class="token string">"Invoke db:migrate task and ignore errors caused by parallel execution."</span>  task try_migrate<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token symbol">:wait_for_setup_completion</span><span class="token punctuation">]</span> <span class="token keyword">do</span>    <span class="token keyword">begin</span>      <span class="token constant">Rake</span><span class="token punctuation">:</span><span class="token symbol">:Task</span><span class="token punctuation">[</span><span class="token string">"db:migrate"</span><span class="token punctuation">]</span><span class="token punctuation">.</span>invoke    <span class="token keyword">rescue</span> <span class="token constant">ActiveRecord</span><span class="token punctuation">:</span><span class="token symbol">:ConcurrentMigrationError</span> <span class="token operator">=</span><span class="token operator">></span> e      <span class="token constant">Rails</span><span class="token punctuation">.</span>logger<span class="token punctuation">.</span>info <span class="token string">"Skip migrations because another migration process is currently running."</span>    <span class="token keyword">end</span>  <span class="token keyword">end</span><span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>行数は多いですがやっていることは<code>rails db:setup</code>の完了を待ってから<code>rails db:migrate</code>を実行しているだけです。</p><p>ただし、<code>db:migrate</code>タスクで<code>ActiveRecord::ConcurrentMigrationError</code>という例外が発生した場合は、エラー終了しないように捕捉してINFOレベルでログに書き出すようにしています。</p><p>この例外は、マイグレーションが多重起動された際に、ロックを取れなかったプロセス側が発生させる例外で、実害はありません。単にそのプロセスではマイグレーションがスキップされたということを示しています。</p><h2 id="アプリイメージの更新とローリングリスタートの動作確認"><a href="#アプリイメージの更新とローリングリスタートの動作確認" class="headerlink" title="アプリイメージの更新とローリングリスタートの動作確認"></a>アプリイメージの更新とローリングリスタートの動作確認</h2><p>Step2での修正は<code>Job</code>の追加と<code>puma</code>用の<code>Deployment</code>の定義の変更の2点だけですが、これを機に<code>demoapp</code>のイメージを更新して<code>puma</code>用の<code>Deployment</code>に反映し、<code>Pod</code>がローリングリスタートされる様子を確認してみましょう。<code>demoapp</code>の更新は新規マイグレーションを含む内容にしてみます。</p><p>まずStep2のマニフェストで<code>demoapp</code>をminikubeにデプロイします。</p><pre><code># Railsアプリのイメージをビルド$ (eval $(minikube docker-env) &amp;&amp; docker build . -t demoapp:0.0.1)$ cd k8s/manifests-step2# APIオブジェクトを作成$ cat *.yaml | kubectl apply -f -# puma deploymentの起動が完了するまで待機$ kubectl rollout status deploy demoapp-puma# puma serviceのエンドポイントをブラウザでオープン$ minikube service demoapp-puma</code></pre><p>次に、<code>messages</code>テーブルに<code>likes</code>というカラムを追加するだけの内容で新しいマイグレーションを作ります。</p><pre><code># RAILS_ROOTに移動してローカル環境にMySQLとRedisを起動しDBを初期化$ cd ../../$ rm -r tmp/mysql tmp/redis$ docker-compose up -d# しばらく待ってから$ ./bin/rails db:setup# 新しいマイグレーションを作成して、ローカル環境にマイグレーション実行$ ./bin/rails g migration AddLiksToMessage likes:integer$ ./bin/rails db:migrate</code></pre><p>新しい<code>demoapp</code>のイメージを作りデプロイします。</p><pre><code># タグを0.0.2に変えてRailsアプリのイメージをビルド$ (eval $(minikube docker-env) &amp;&amp; docker build . -t demoapp:0.0.2)# 新しいイメージをデプロイして更新が完了するのを待つ$ kubectl set image deploy/demoapp-puma puma=demoapp:0.0.2$ kubectl set image deploy/demoapp-sidekiq sidekiq=demoapp:0.0.2$ kubectl rollout status deploy demoapp-puma$ kubectl rollout status deploy demoapp-sidekiq</code></pre><p>事前に別の端末で下記のコマンドをそれぞれ実行しておくとローリングリスタートに伴う<code>Pod</code>の入れ替わりの様子を確認しやすいと思います。</p><pre><code># puma用のPodのログを表示$ stern &quot;demoapp-puma-.*&quot;# APIオブジェクトの変化を監視して表示$ kubectl get deployments --watch$ kubectl get replicasets --watch$ kubectl get pods --watch</code></pre><p>watchコマンドを用いる方法もおすすめです。watchコマンドは <code>brew install watch</code> でインストールできます。</p><pre><code># 更新頻度を1秒に指定$ watch -n 1 kubectl get deployments,replicasets,pods</code></pre><p><code>kubectl set image deploy/demoapp-puma puma=demoapp:0.0.2</code>を実行すると<code>puma</code>の<code>Deployment</code>が更新され、ローリングアップデートが開始します(<code>puma=demoapp:0.0.2</code>の<code>puma</code>は、<code>puma-deploy.yaml</code>で定義したコンテナの<code>name</code>です)。</p><p>この際、<code>puma-deploy.yaml</code>の<code>replicas</code>が2だと下記のような動作になり、<code>rails db:migrate</code>の競合は起きません。</p><ol><li>新しい<code>Pod</code>を一つ起動開始</li><li>1の<code>Pod</code>が起動完了し、古い<code>Pod</code>のうち一つが停止し、さらに新しい<code>Pod</code>を一つ起動開始</li><li>2の<code>Pod</code>が起動完了し、残りの古い<code>Pod</code>が停止</li></ol><p><code>replicas</code>が4だと下記のような動作になり、高い確率で<code>rails db:migrate</code>が競合します。(<code>puma-deploy.yaml</code>を編集して試してみてください)</p><ol><li>古い<code>Pod</code>を一つ停止すると同時に新しい<code>Pod</code>を二つ起動開始</li><li>1の<code>Pod</code>二つがほぼ同時に起動完了し、古い<code>Pod</code>がさらに停止され、新しい<code>Pod</code>がさらに二つ起動開始</li><li>2の<code>Pod</code>二つがほぼ同時に起動完了し、最後の<code>Pod</code>が停止</li></ol><p><code>rails db:migrate</code>がほぼ同時に起動された場合には、遅い方の<code>Pod</code>のログに下記のようなメッセージが残るはずです。</p><pre><code>INFO -- : Skip migrations because another migration process is currently running.</code></pre><p>なお、<code>Deployment</code>の更新の際の<code>Pod</code>の入れ替えはこのように徐々に新しいものに入れ替えるような動作になっていますが、これはマニフェストの<code>.spec.strategy</code>で変更することができます。<code>puma-deploy.yaml</code>の例では特に指定していないのでデフォルトの<code>RollingUpdate</code>になっていますが、<code>Recreate</code>を指定すると古い<code>Pod</code>を全て削除してから新しい<code>Pod</code>を立ち上げるようになります。これについてはStep3で取り上げます。</p><p>また、<code>replicas</code>が4のケースだと、一時的に起動中の<code>Pod</code>の数が5つに、READYな状態の<code>Pod</code>は3つになっている点に注意してください。急激なサービスのスループット低下とインフラ負荷の増加を防ぐため、レプリカの数が多い場合はデフォルトでこのような動作をするようになっています。ローリングアップデート中におけるREADYな状態の<code>Pod</code>の割合と起動中(準備中含む)の<code>Pod</code>の割合は、それぞれ<code>.spec.strategy.rollingUpdate.maxUnavailable</code>と<code>.spec.strategy.rollingUpdate.maxSurge</code>で指定できます。デフォルト値はいずれも25%です。これについては本ドキュメントではこれ以上説明しないので、詳細は下記の資料を参照してください。</p><p><a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#writing-a-deployment-spec" target="_blank" rel="noopener">Writing a Deployment Spec</a></p><h2 id="graceful-stop"><a href="#graceful-stop" class="headerlink" title="graceful stop"></a>graceful stop</h2><p>k8sのようにBlue-Greenデプロイメントでアプリケーションの更新を行う場合、新しいインスタンスを立ち上げつつ古い方のインスタンスは順次停止します。この際、実行中の処理はできるだけ強制終了せずに、完了するまで待ってからアプリケーションプロセスを停止することが望ましいです。</p><p>pumaとsidekiqの場合は次のようなことを意味します。</p><ul><li>puma: 処理中のリクエストのレスポンスを返し終わってから停止する。</li><li>sidekiq: 処理中のジョブが全て完了してから停止する。</li></ul><p>一般的にこのような停止処理のことをgraceful stopと呼びます。</p><p>前節で述べたローリングリスタートの際には、k8sは停止する<code>Pod</code>の全てのコンテナに<code>SIGTERM</code>シグナルを送信し、コンテナのメインプロセスが停止するのを待ちます。そのため、コンテナで起動するプロセスは<code>SIGTERM</code>を受け取った時にgraceful stopするように実装しておく必要があります。</p><p>pumaとsidekiqはいずれも<code>SIGTERM</code>を受け取った際にgraceful stopする仕様なので、このままでも問題ない場合もあるのですが、下記の点に気をつける必要があります。</p><ul><li>シェルスクリプトでラップしている場合は、<code>trap</code>コマンドなどでシグナルを捕捉して子プロセスにも<code>SIGTERM</code>を送ること。サンプルは<a href="/blog/2018/05/30/docker-compose-with-rails/#docker-compose%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AB%E3%82%88%E3%82%8B%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%83%97%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC%E7%92%B0%E5%A2%83">第2回</a>を参照してください。</li><li>sidekiqは<code>SIGTERM</code>を受け取ってからジョブの強制終了まで8秒しか待たない。これが短すぎる場合は起動時の<code>-t</code>オプションで変更しておくこと。サンプルは<a href="/blog/2018/05/30/kubernetes-with-rails/#sidekiq-deploy-yaml-sidekiq-の-Deployment">第4回</a>の記事を参照してください。</li><li>k8sがコンテナを強制停止するまでの猶予期間が30秒で足りない場合は、<code>Deployment</code>の<code>spec.template.spec.terminationGracePeriodSeconds</code>で変更しておくこと。(<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#pod-v1-core" target="_blank" rel="noopener">参考</a>)</li></ul><p>pumaとsidekiqのシグナルの扱いについては下記のドキュメントを参照してください。</p><ul><li><a href="https://github.com/puma/puma/blob/master/docs/signals.md" target="_blank" rel="noopener">puma/signals.md</a></li><li><a href="https://github.com/mperham/sidekiq/wiki/Signals" target="_blank" rel="noopener">Signals mperham/sidekiq Wiki</a></li></ul><h2 id="Makefileのサンプル"><a href="#Makefileのサンプル" class="headerlink" title="Makefileのサンプル"></a>Makefileのサンプル</h2><p>Step2のディレクトリにもMakefileを置いてあります。本節に出てきた各種操作は(マイグレーションの作成以外は)対応するタスクを定義してあるので、ローリングアップデートの動作を確認する際には<code>make</code>コマンドを使うと簡単にオペレーションを実行することができます。</p><pre><code>$ cd k8s/manifests-step2# タグを指定してイメージをビルドし、APIオブジェクト一式を作成(TAGは省略可)$ make TAG=0.0.1# タグを指定してイメージをビルドし、puma用Deploymentに反映$ make TAG=0.0.2 update# APIオブジェクトの削除$ make clean</code></pre><p>MakefileはStep1のものをベースにいくつか修正を加えた内容です。<code>miniube-docker-build</code>は、指定されたタグが存在する場合はビルドをスキップするようにしています。</p><pre class="line-numbers language-makefile"><code class="language-makefile"><span class="token comment" spellcheck="true"># k8s/manifests-step2/Makefile</span>SHELL <span class="token operator">=</span> /bin/bash<span class="token keyword">ifeq</span> <span class="token punctuation">(</span><span class="token variable">$</span><span class="token punctuation">(</span>TAG<span class="token punctuation">)</span>,<span class="token punctuation">)</span>    tag <span class="token operator">:=</span> 0.0.1<span class="token keyword">else</span>    tag <span class="token operator">:=</span> <span class="token variable">$</span><span class="token punctuation">(</span>TAG<span class="token punctuation">)</span><span class="token keyword">endif</span><span class="token symbol">all</span><span class="token punctuation">:</span>    <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> minikube-docker-build    <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> kubectl-apply    <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> kubectl-rollout-status    <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> minikube-service<span class="token symbol">clean</span><span class="token punctuation">:</span> kubectl-delete<span class="token symbol">update</span><span class="token punctuation">:</span>     <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> TAG<span class="token operator">=</span><span class="token variable">$</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span> minikube-docker-build    <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> TAG<span class="token operator">=</span><span class="token variable">$</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span> deploy<span class="token symbol">minikube-docker-build</span><span class="token punctuation">:</span>    eval <span class="token variable">$$</span><span class="token punctuation">(</span>minikube docker-env<span class="token punctuation">)</span> &amp;&amp; \        if [ <span class="token string">"$$(docker image ls -q demoapp:$(tag))"</span> <span class="token operator">=</span><span class="token operator">=</span> <span class="token string">""</span> ]<span class="token punctuation">;</span> then \<span class="token symbol">            docker build ../../ -t demoapp</span><span class="token punctuation">:</span><span class="token variable">$</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span><span class="token punctuation">;</span> \        fi<span class="token symbol">kubectl-apply</span><span class="token punctuation">:</span>    cat *.yaml <span class="token operator">|</span> kubectl apply -f -<span class="token symbol">kubectl-rollout-status</span><span class="token punctuation">:</span>    kubectl rollout status deploy demoapp-puma    kubectl rollout status deploy demoapp-sidekiq<span class="token symbol">minikube-service</span><span class="token punctuation">:</span>    minikube service demoapp-puma<span class="token symbol">kubectl-delete</span><span class="token punctuation">:</span>    cat *.yaml <span class="token operator">|</span> kubectl delete -f -<span class="token symbol">deploy</span><span class="token punctuation">:</span>    kubectl set image deploy/demoapp-puma puma<span class="token operator">=</span>demoapp<span class="token punctuation">:</span><span class="token variable">$</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span>    kubectl set image deploy/demoapp-sidekiq sidekiq<span class="token operator">=</span>demoapp<span class="token punctuation">:</span><span class="token variable">$</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span>    kubectl rollout status deploy demoapp-puma    kubectl rollout status deploy demoapp-sidekiq<span class="token symbol">stern</span><span class="token punctuation">:</span>    stern demoapp.*<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="Step3-PersistentVolumeClaim-PVC"><a href="#Step3-PersistentVolumeClaim-PVC" class="headerlink" title="Step3: PersistentVolumeClaim(PVC)"></a>Step3: PersistentVolumeClaim(PVC)</h1><p>サンプルコードは全て下記のディレクトリにあります。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step3/" target="_blank" rel="noopener">k8s/manifests-step3/</a></p><p>Step2の時点ではMySQLとRedisのデータ領域が永続化されていないため、何らかの障害で<code>Pod</code>が停止するとDBの内容が全て消えるという制約があります。</p><p>Step2のマニフェストでAPIオブジェクト一式を作成した後、<code>mysql</code>の<code>Pod</code>を<code>kubectl delete</code>コマンドで削除すると、<code>puma</code>のヘルスチェックが失敗するようになるのを確認できます。</p><pre><code>$ cd k8s/manifests-step2# APIオブジェクトを作成$ cat *.yaml | kubectl apply -f -# puma deploymentの起動が完了するまで待機$ kubectl rollout status deploy demoapp-puma# MySQL用のPodを削除$ kubectl delete pod -l &quot;app=demoapp,component=mysql&quot;# pumaのログを確認$ stern &quot;demoapp-puma-.*&quot;(省略)demoapp-puma-5cdbdbfc76-d6ftm puma I, [2018-05-13T12:11:56.303988 #33]  INFO -- : [026a124c-fedb-4423-8587-40bec3481cde] health_check failed:demoapp-puma-5cdbdbfc76-d6ftm pumademoapp-puma-5cdbdbfc76-d6ftm puma Migrations are pending. To resolve this issue, run:demoapp-puma-5cdbdbfc76-d6ftm pumademoapp-puma-5cdbdbfc76-d6ftm puma         bin/rails db:migrate RAILS_ENV=production</code></pre><p>そこでStep3では、MySQLとRedisに<code>PersistentVolume</code>(以後、<code>PV</code>)というAPIオブジェクトを割り当ててデータボリュームとして使用し、DBの内容が<code>Pod</code>の生死に関わらず維持されるようにします。</p><p><code>PV</code>は永続ストレージを抽象化したAPIオブジェクトです。<code>PV</code>の実体はNFSやGlusterFSのようなネットワークストレージでも良いし、GCP上であればGCEのPersistentDisk、AWSであればEBSを使うこともできます。これをボリュームタイプと呼びます。</p><p><code>PV</code>のボリュームタイプはプラグインとして実装されていて、すでに主要なネットワークストレージとパブリッククラウドのストレージサービスに対応しています。</p><p><a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes" target="_blank" rel="noopener">Types of Persistent Volumes</a></p><p>MySQLのようなデータベースをk8sで運用するためには、障害で<code>Pod</code>やk8sノードがクラッシュした場合に、新しい<code>Pod</code>にデータ領域を引き継ぐ必要があります。k8sでは先に挙げたようなドライバ経由でk8sクラスタの外にデータを保存することによってこれを実現しています。</p><p><code>PV</code>オブジェクトは直接マニフェストを書いて作ることもできますが、<code>PersistentVolumeClaim</code>(以後、<code>PVC</code>)というAPIオブジェクトの<a href="https://kubernetes.io/docs/concepts/storage/dynamic-provisioning/" target="_blank" rel="noopener">Dynamic Provisioning</a>という機能を経由して作る方が簡単なので、本ドキュメントではその方法で説明します。</p><p>まずMySQL用の<code>PVC</code>オブジェクトのマニフェスト <code>mysql-pvc.yaml</code> を作成します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step3/mysql-pvc.yaml</span><span class="token punctuation">---</span><span class="token key atrule">kind</span><span class="token punctuation">:</span> PersistentVolumeClaim<span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> mysql<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">accessModes</span><span class="token punctuation">:</span>    <span class="token punctuation">-</span> ReadWriteOnce          <span class="token comment" spellcheck="true"># 同時に一つのプロセスからしか読み書きしないモードを指定</span>  <span class="token key atrule">resources</span><span class="token punctuation">:</span>    <span class="token key atrule">requests</span><span class="token punctuation">:</span>      <span class="token key atrule">storage</span><span class="token punctuation">:</span> 8Gi           <span class="token comment" spellcheck="true"># 確保する永続ストレージのサイズを指定</span>  <span class="token key atrule">storageClassName</span><span class="token punctuation">:</span> standard <span class="token comment" spellcheck="true"># 後述</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>.spec.storageClassName</code>には<code>StorageClass</code>というAPIオブジェクトの名前を指定します。<code>StorageClass</code>の詳細については後述します。</p><p>次に <code>mysql-deploy.yaml</code> へこの<code>PVC</code>を結びつけます。コメントをつけている行は、Step2での定義に追加した部分です。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step3/mysql-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> mysql<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">1</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp      <span class="token key atrule">component</span><span class="token punctuation">:</span> mysql  <span class="token key atrule">strategy</span><span class="token punctuation">:</span>            <span class="token comment" spellcheck="true"># 更新時の動作を指定</span>    <span class="token key atrule">type</span><span class="token punctuation">:</span> Recreate     <span class="token comment" spellcheck="true"># 古いPodを停止してから新しいPodを起動</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">labels</span><span class="token punctuation">:</span>        <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp        <span class="token key atrule">component</span><span class="token punctuation">:</span> mysql    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always      <span class="token key atrule">volumes</span><span class="token punctuation">:</span>                          <span class="token comment" spellcheck="true"># </span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> data                    <span class="token comment" spellcheck="true"># .volumeMountsの.nameで指定</span>          <span class="token key atrule">persistentVolumeClaim</span><span class="token punctuation">:</span>        <span class="token comment" spellcheck="true">#</span>            <span class="token key atrule">claimName</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql    <span class="token comment" spellcheck="true"># PVCオブジェクトの.metadata.nameを指定</span>      <span class="token key atrule">initContainers</span><span class="token punctuation">:</span>                   <span class="token comment" spellcheck="true"># 前処理用のコンテナ</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">"remove-lost-found"</span>     <span class="token comment" spellcheck="true"># データディレクトリの"lost+found"ディレクトリを削除</span>          <span class="token key atrule">image</span><span class="token punctuation">:</span> <span class="token string">"busybox:1.25.0"</span>          <span class="token key atrule">command</span><span class="token punctuation">:</span>             <span class="token punctuation">-</span> rm            <span class="token punctuation">-</span> <span class="token punctuation">-</span>fr            <span class="token punctuation">-</span> /var/lib/mysql/lost+found          <span class="token key atrule">volumeMounts</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> data              <span class="token key atrule">mountPath</span><span class="token punctuation">:</span> /var/lib/mysql      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> mysql          <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.21          <span class="token key atrule">livenessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">tcpSocket</span><span class="token punctuation">:</span>              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3306</span>          <span class="token key atrule">readinessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">tcpSocket</span><span class="token punctuation">:</span>              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3306</span>          <span class="token key atrule">volumeMounts</span><span class="token punctuation">:</span>                 <span class="token comment" spellcheck="true">#</span>            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> data                <span class="token comment" spellcheck="true"># .volumesの.nameを指定</span>              <span class="token key atrule">mountPath</span><span class="token punctuation">:</span> /var/lib/mysql <span class="token comment" spellcheck="true"># MySQLのデータディレクトリを指定</span>          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql<span class="token punctuation">-</span>env            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql<span class="token punctuation">-</span>env<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>このように<code>PVC</code>オブジェクトを指定すると、<code>Deployment</code>の作成時に<code>StorageClass</code>に応じた<code>PV</code>が自動的に作成され、指定したパスにマウントされます。この例では <code>mysql-pvc.yaml</code>で定義した<code>PVC</code>によって作られた<code>PV</code>をMySQLのデータディレクトリである<code>/var/lib/mysql</code>にマウントしています。</p><p><code>.spec.template.spec.initContainers</code>は前処理用のコンテナの定義です。メインのコンテナを起動する前に自動的に実行されます。この例ではMySQLのデータディレクトリである<code>/var/lib/mysql</code>ディレクトリに<code>lost+found</code>というディレクトリがあれば削除するという処理を入れています。<code>PVC</code>で確保したボリュームの中身は後述する<code>StorageClass</code>によって異なる場合があるのですが、<code>mysql</code>イメージは<code>/var/lib/mysql</code>ディレクトリが空でない場合、DBの初期化処理がエラーで失敗します。minikubeの場合は空なのですが、GKEのように<code>lost+found</code>ディレクトリが最初から存在する場合もあるので、ある程度どこでも動くように<code>initContainers</code>でこれを取り除いています。</p><p>また、<code>.spec.strategy.type</code>に<code>Recreate</code>を指定している点に注意してください。Step2でも説明しましたが、これは更新時の<code>Pod</code>の入れ替えの挙動を指定するパラメータです。デフォルトは<code>RollingUpdate</code>になっており、古い<code>Pod</code>を停止する前に新しい<code>Pod</code>を起動します。MySQLは同一のデータディレクトリを複数のサーバが参照するとエラーになるため、<code>Recreate</code>を指定して二つ以上の<code>Pod</code>が同時に起動しないようにしています。</p><p>Redisについてもほぼ同様に、<code>redis-pvc.yaml</code>を追加して<code>redis-deploy.yaml</code>を更新しています。特筆すべき点はないので説明は割愛します。詳細はサンプルコードを参照してください。</p><ul><li><a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step3/redis-pvc.yaml" target="_blank" rel="noopener">k8s/manifests-step3/redis-pvc.yaml</a></li><li><a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step3/redis-deploy.yaml" target="_blank" rel="noopener">k8s/manifests-step3/redis-deploy.yaml</a></li></ul><h2 id="StorageClassについて"><a href="#StorageClassについて" class="headerlink" title="StorageClassについて"></a>StorageClassについて</h2><p><code>StorageClass</code>は、永続ストレージの設定をまとめたオブジェクトです。設定とはボリュームタイプや<code>PV</code>の削除時にデータオブジェクトを残すかどうかといった項目です。minikubeの場合、クラスタの作成時に自動的に<code>standard</code>という名前の<code>StorageClass</code>が作られるのでそれを指定しています。このオブジェクトの定義は、<code>minikube dashboard</code>上または下記のコマンドで確認できます。</p><pre><code>$ kubectl get storageclass standard -o yaml</code></pre><p>重要な部分を抜粋すると下記のような内容です。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> storage.k8s.io/v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> StorageClass<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">annotations</span><span class="token punctuation">:</span>    <span class="token key atrule">storageclass.beta.kubernetes.io/is-default-class</span><span class="token punctuation">:</span> <span class="token string">"true"</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> standard<span class="token key atrule">provisioner</span><span class="token punctuation">:</span> k8s.io/minikube<span class="token punctuation">-</span>hostpath<span class="token key atrule">reclaimPolicy</span><span class="token punctuation">:</span> Delete<span class="token key atrule">volumeBindingMode</span><span class="token punctuation">:</span> Immediate<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>.provisioner</code>がボリュームプラグインの種類を示しています。<code>minikube-hostpath</code>はシングルノードでの検証用のもので、minikube VM上のローカルストレージをデータの保存先として使用します。</p><p>GCPのGKEでk8sクラスタを作った場合も、自動的に<code>standard</code>という名前の<code>StorageClass</code>が作られますが、下記の通り<code>.provisioner</code>はGCEのPersistentDiskが指定されているため、マルチノードクラスタでの運用に耐えるものとなっています。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> storage.k8s.io/v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> StorageClass<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">annotations</span><span class="token punctuation">:</span>    <span class="token key atrule">storageclass.beta.kubernetes.io/is-default-class</span><span class="token punctuation">:</span> <span class="token string">"true"</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> standard<span class="token key atrule">provisioner</span><span class="token punctuation">:</span> kubernetes.io/gce<span class="token punctuation">-</span>pd<span class="token key atrule">parameters</span><span class="token punctuation">:</span>  <span class="token key atrule">type</span><span class="token punctuation">:</span> pd<span class="token punctuation">-</span>standard<span class="token key atrule">reclaimPolicy</span><span class="token punctuation">:</span> Delete<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>重要なのは、<code>PVC</code>オブジェクトでは単に<code>standard</code>という名前だけを指定しており、ボリュームプラグインのようなクラスタ環境に依存するパラメータを一切記述していないという点です。これにより、この例のようにminikubeとGKEのような異なるプラットフォームであっても、それぞれの環境に応じて適切に設定された<code>StorageClass</code>が同じ名前で用意されていれば、このマニフェストはいずれのクラスタにもデプロイ可能になります。</p><h2 id="StatefulSetについて"><a href="#StatefulSetについて" class="headerlink" title="StatefulSetについて"></a>StatefulSetについて</h2><p>k8sには<code>StatefulSet</code>というAPIオブジェクトがあります。このオブジェクトはステートフルなアプリケーションを管理するためのものと位置付けられています。</p><p><a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/" target="_blank" rel="noopener">StatefulSets</a></p><p>そのため、一見するとMySQLやRedisのようなサービスを管理する際には<code>Deployment</code>ではなく<code>StatefulSet</code>を使うべきではと思われるのですが、<code>StatefulSet</code>が必要になるのは <strong>Set</strong> の名が示す通りMySQLのマスター/スレーブ構成など複数のコンテナでクラスタを組む場合です。</p><p>Step3の例のように単一のコンテナによるステートフルなアプリケーションの場合、<code>Deployment</code>を使って<code>.spec.strategy.type</code>を<code>Recreate</code>にするだけで十分です。</p><p>ということが公式のドキュメントにも書いてあるのでリンクを貼っておきます。<br><a href="https://kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-application/" target="_blank" rel="noopener">Run a Single-Instance Stateful Application</a></p><h2 id="ステートフルなサービスの運用について"><a href="#ステートフルなサービスの運用について" class="headerlink" title="ステートフルなサービスの運用について"></a>ステートフルなサービスの運用について</h2><p>Railsアプリケーションをクラウド上のk8sクラスタ上で運用する場合には、MySQLやRedisのような永続データを持つサービスはコンテナ化せず、クラウドベンダのマネージドサービスを使うという方法も考えられます。</p><p>特に可用性などの要件が厳しい場合には、簡単にマルチAZでマスタースレーブ構成を運用できるAmazon RDSのようなサービスは魅力的な選択肢です。</p><p>一方、可用性やデータロスト耐性に対する要求がそこまで厳しくない場合には、Step3で示したような構成も悪くない選択肢です。<code>Pod</code>やk8sノードに障害が発生した場合には、新しい<code>Pod</code>が即座に作成されて永続ボリュームの内容を引き継ぎます。Amazon EBSやGCE Persistent Diskのようなストレージサービスを永続ボリュームとして使う場合、定期的なスナップショットを取るのは簡単です。</p><p>確かにマルチ構成のAmazon RDSと比較すると障害発生時のダウンタイムは長くなる可能性が高いし、永続ボリュームに障害が発生した場合には直前のスナップショットまでデータロストするリスクがありますが、それを許容できるような場合にはこの程度の構成で十分とも言えます。</p><h1 id="Step4-Ingress"><a href="#Step4-Ingress" class="headerlink" title="Step4: Ingress"></a>Step4: Ingress</h1><p>サンプルコードは全て下記のディレクトリにあります。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step4/" target="_blank" rel="noopener">k8s/manifests-step4/</a></p><p>Step3の時点では、Railsアプリ(<code>puma</code>プロセス)の外部向けのインタフェースとして、<code>NodePort</code>タイプの<code>Service</code>オブジェクトを使っていました。この方法ではk8sクラスタを構成する全てのノードにエンドポイントが用意されます。minikubeのようにシングルノード構成の検証用クラスタではこれで十分ですが、マルチノード構成の本番環境ではそのまま運用するのは難しいでしょう。少なくとも負荷分散や可用性担保のためには前段にロードバランサが必要になります。そうするとノードの追加や縮退の際にロードバランサの設定も変更する必要が生じ、管理が煩雑になります。</p><p>そのため、このような用途で<code>Service</code>オブジェクトを使う場合には、通常は<code>LoadBalancer</code>というタイプを指定します。</p><p><code>LoadBalancer</code>を指定した場合にどのような仕組みで接続用のエンドポイントが用意されるかはk8sのデプロイ先の環境によって異なります。例えばAWS上であればELBを使うことができます。一方、minikubeは<code>LoadBalancer</code>に完全には対応していません。そのため、<code>LoadBalancer</code>タイプで<code>Service</code>を作ると下記のように<code>EXTERNAL-IP</code>がいつまでも<code>pending</code>の状態のまま変わらず、ダッシュボードでも準備中のアイコンが表示され続けます。</p><pre><code>% kubectl get svcNAME            TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGEdemoapp-mysql   ClusterIP      10.99.99.47      &lt;none&gt;        3306/TCP         1mdemoapp-puma    LoadBalancer   10.98.211.117    &lt;pending&gt;     3000:32320/TCP   1mdemoapp-redis   ClusterIP      10.111.218.160   &lt;none&gt;        6379/TCP         1mkubernetes      ClusterIP      10.96.0.1        &lt;none&gt;        443/TCP          8m</code></pre><p>参考: <a href="https://stackoverflow.com/questions/44110876/kubernetes-service-external-ip-pending" target="_blank" rel="noopener">docker - kubernetes service external ip pending - Stack Overflow</a></p><p>一方、k8sには負荷分散やSSLターミネーション、ホスト名やリクエストパスによるバーチャルホストなど、外部向けのエンドポイントを抽象化することにより特化した機能を持つ<code>Ingress</code>というオブジェクトがあります。これらの機能の一部は<code>LoadBalancer</code>タイプの<code>Service</code>でも実現できますが、AWSのELBなどバックエンドごとに固有の<code>annotation</code>で指定する必要があり、<code>Ingress</code>に比べると可搬性の面で劣ります。</p><p>そこでこのStep4では外部向けのエンドポイントに<code>Ingress</code>を使うように構成を変更します。<code>LoadBalancer</code>タイプの<code>Service</code>の使い方については説明しません。詳細は下記のドキュメントを参照してください。</p><ul><li><a href="https://kubernetes.io/docs/concepts/services-networking/ingress/" target="_blank" rel="noopener">Ingress | Kubernetes</a></li><li><a href="https://kubernetes.io/docs/concepts/services-networking/service/#type-loadbalancer" target="_blank" rel="noopener">Services | Kubernetes</a></li></ul><h2 id="Ingress-コントローラのインストール"><a href="#Ingress-コントローラのインストール" class="headerlink" title="Ingress コントローラのインストール"></a>Ingress コントローラのインストール</h2><p><code>PersistentVolume</code>同様、<code>Ingress</code>もまたその実装がk8sクラスタのデプロイ先に依存する機能です。GKEではGCPのネットワーク機能を使って実装されていますが、その他の環境では<code>Ingress</code>コントローラを<code>Pod</code>としてk8sクラスタ上に動かしておく必要があります。</p><p><a href="https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers" target="_blank" rel="noopener">Ingress controllers</a></p><p><code>Ingress</code>コントローラの中には<a href="https://github.com/kubernetes/ingress-gce" target="_blank" rel="noopener">GLBC(ingress-gce)</a>のようにクラウドプロバイダ固有の機能を使ったものと、<a href="https://github.com/kubernetes/ingress-nginx" target="_blank" rel="noopener">NGINX Ingress Controller(ingress-nginx)</a>のようにどこでも動作するものがあります。</p><p>幸いにしてminikubeでは<code>NGINX Ingress Controller</code>をaddonとして簡単にインストールすることができます。本ドキュメントで示すマニフェストを試す場合には、minikubeインスタンスの起動後に下記のコマンドを実行してください。</p><pre><code>$ minikube addons enable ingress</code></pre><h2 id="動作確認の手順"><a href="#動作確認の手順" class="headerlink" title="動作確認の手順"></a>動作確認の手順</h2><p>このドキュメントでは、下記のような構成を目指します。</p><ul><li><code>puma</code>サービスへ <code>https://demoapp-puma.$(minikube ip).nip.io/</code> というホスト名で接続できるようにする。</li><li>サーバ証明書には自己署名のダミー証明書を使用する。</li><li><code>Ingress</code>でSSL終端して、<code>Service</code>にはHTTPで接続する。</li></ul><p>Step3以前と比べて少し手順が変わっています。各コマンドの詳細は次節以降で説明します。</p><pre><code># Railsアプリのイメージをビルド$ (eval $(minikube docker-env) &amp;&amp; docker build . -t demoapp:0.0.1)$ cd k8s/manifests-step4# ダミーのサーバ証明書を作成COMMON_NAME=demoapp-puma.$(minikube ip).nip.ioopenssl req -new -x509 -nodes -keyout server.key -days 3650 \  -subj &quot;/CN=${COMMON_NAME}&quot; \  -extensions v3_req \  -config &lt;(cat openssl.conf | sed s/\${COMMON_NAME}/$COMMON_NAME/) &gt; server.pemunset COMMON_NAME# 証明書をSecretオブジェクトとして登録kubectl create secret tls demoapp-puma-tls --key server.key --cert server.pem# APIオブジェクトを作成export MINIKUBE_IP=$(minikube ip)cat *.yaml | sed s/\${MINIKUBE_IP}/$MINIKUBE_IP/ | kubectl apply -f -# puma deploymentの起動が完了するまで待機$ kubectl rollout status deploy demoapp-puma# puma Ingressのエンドポイントをブラウザでオープン$ open https://demoapp-puma.$(minikube ip).nip.io/# 作成したAPIオブジェクトを削除cat *.yaml | kubectl delete -f -kubectl delete secret demoapp-puma-tls</code></pre><p>Makefileを置いてあるので下記のコマンドで代替できます。</p><pre><code>$ cd k8s/manifests-step4# Railsアプリのイメージをビルド$ make minikube-docker-build# ダミーのサーバ証明書を作成しSecretオブジェクトとして登録$ make kubectl-create-secret-tls# APIオブジェクトを作成$ make kubectl-apply# puma deploymentの起動が完了するまで待機$ make kubectl-rollout-status# puma Ingressのエンドポイントをブラウザでオープン$ make open</code></pre><p>単に<code>make</code>とだけ入力すると順番に全部実行します。</p><pre><code>$ make</code></pre><h2 id="ダミーのサーバ証明書の作成"><a href="#ダミーのサーバ証明書の作成" class="headerlink" title="ダミーのサーバ証明書の作成"></a>ダミーのサーバ証明書の作成</h2><p>HTTPSで接続できるようにするためにはサーバ証明書が必要です。<a href="https://github.com/jetstack/kube-lego" target="_blank" rel="noopener">kube-lego</a>というモジュールを使えば<code>Ingress</code>とLet’s Encryptを連携して動的に証明書を作成するようなこともできるのですが、今回はもっとシンプルに<code>openssl</code>コマンドで自己署名証明書(いわゆるオレオレ証明書)を作ります。</p><p>まず下記のような内容でopensslの設定ファイルを用意します。Chromeではバージョン58以降、subjectAltName(SAN)が設定されていない証明書はエラー扱いになりました。下記の設定はSANを設定するためのものです。</p><pre class="line-numbers language-toml"><code class="language-toml"># k8s/manifests-step4/openssl.conf[req]distinguished_name=req_distinguished_namereq_extensions=v3_req[v3_req]basicConstraints=CA:FALSEkeyUsage=nonRepudiation, digitalSignature, keyEnciphermentsubjectAltName=@alt_names[req_distinguished_name][alt_names]DNS.1=${COMMON_NAME}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>次に下記のようなコマンドで証明書(<code>server.pem</code>)と鍵(<code>server.key</code>)を作成します。</p><pre><code>COMMON_NAME=demoapp-puma.$(minikube ip).nip.ioopenssl req -new -x509 -nodes -keyout server.key -days 3650 \  -subj &quot;/CN=${COMMON_NAME}&quot; \  -extensions v3_req \  -config &lt;(cat openssl.conf | sed s/\${COMMON_NAME}/$COMMON_NAME/) &gt; server.pemunset COMMON_NAME</code></pre><p>ホスト名は前述の通り<code>demoapp-puma.$(minikube ip).nip.io/</code>とします。<code>$(minikube ip)</code>部分はminikubeインスタンスの立ち上げごとに変わる可能性があるため、設定ファイルはテンプレートとして扱い、<code>${COMMON_NAME}</code>と記述されている部分を<code>sed</code>コマンドで書き換えています。</p><p>内容は下記のコマンドで確認できます。</p><pre><code>$ openssl x509 -text &lt; server.pem(一部抜粋)Certificate:    Signature Algorithm: sha256WithRSAEncryption        Issuer: CN=demoapp-puma.192.168.64.25.nip.io        Subject: CN=demoapp-puma.192.168.64.25.nip.io        X509v3 extensions:            X509v3 Subject Alternative Name:                DNS:demoapp-puma.192.168.64.25.nip.io</code></pre><p>macOSの場合、<code>server.pem</code>をキーチェーンに追加して信頼するようにしておけばブラウザの警告を抑止できます。後で忘れずに削除してください。</p><p>なお、Step4にもMakefileを置いているので、証明書の作成は下記のコマンドで代替できます。</p><pre><code>$ cd k8s/manifests-step4$ make server.pem# minikube ipが変わったら必ず作り直すこと$ make --always-make server.pem</code></pre><h2 id="証明書用のSecretオブジェクトの作成"><a href="#証明書用のSecretオブジェクトの作成" class="headerlink" title="証明書用のSecretオブジェクトの作成"></a>証明書用の<code>Secret</code>オブジェクトの作成</h2><p><code>Ingress</code>で先ほど作成した証明書と鍵を使うためには、<code>Secret</code>オブジェクトに登録する必要があります。これまでに紹介した<code>Secret</code>オブジェクトのようにYAML形式のマニフェストファイルを書いても良いのですが、TLS用の証明書と鍵をファイルから登録することについては<code>kubectl</code>コマンドがサポートしているため、下記のように登録します。</p><pre><code>kubectl create secret tls demoapp-puma-tls --key server.key --cert server.pem# またはmake kubectl-create-secret-tls</code></pre><h2 id="Ingressオブジェクト"><a href="#Ingressオブジェクト" class="headerlink" title="Ingressオブジェクト"></a><code>Ingress</code>オブジェクト</h2><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step4/puma-ing.yml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> extensions/v1beta1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Ingress<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma  <span class="token key atrule">annotations</span><span class="token punctuation">:</span>    <span class="token key atrule">kubernetes.io/ingress.allow-http</span><span class="token punctuation">:</span> <span class="token string">"false"</span> <span class="token comment" spellcheck="true"># HTTPでのアクセスを禁止</span>    <span class="token comment" spellcheck="true"># GKEで確保済みの静的IPアドレスを指定する場合は下記のようにする</span>    <span class="token comment" spellcheck="true"># kubernetes.io/ingress.global-static-ip-name: rails-k8s-demoapp</span><span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">tls</span><span class="token punctuation">:</span>  <span class="token punctuation">-</span> <span class="token key atrule">hosts</span><span class="token punctuation">:</span>    <span class="token punctuation">-</span> demoapp<span class="token punctuation">-</span>puma.$<span class="token punctuation">{</span>MINIKUBE_IP<span class="token punctuation">}</span>.nip.io     <span class="token comment" spellcheck="true"># このTLS証明書で受けるホスト名のリスト</span>    <span class="token key atrule">secretName</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma<span class="token punctuation">-</span>tls             <span class="token comment" spellcheck="true"># 証明書と鍵のSecretオブジェクトの名前</span>  <span class="token key atrule">rules</span><span class="token punctuation">:</span>  <span class="token punctuation">-</span> <span class="token key atrule">host</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma.$<span class="token punctuation">{</span>MINIKUBE_IP<span class="token punctuation">}</span>.nip.io <span class="token comment" spellcheck="true"># ホスト名</span>    <span class="token key atrule">http</span><span class="token punctuation">:</span>      <span class="token key atrule">paths</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token key atrule">backend</span><span class="token punctuation">:</span>          <span class="token key atrule">serviceName</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma          <span class="token comment" spellcheck="true"># バックエンドのServiceの名前とポート</span>          <span class="token key atrule">servicePort</span><span class="token punctuation">:</span> <span class="token number">3000</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>${MINIKUBE_IP}</code>となっている部分は、<code>kubectl</code>コマンドに渡す前に<code>sed</code>コマンドで置換します。</p><pre><code>$ export MINIKUBE_IP=$(minikube ip)$ cat *.yaml | sed s/\${MINIKUBE_IP}/$MINIKUBE_IP/ | kubectl apply -f -</code></pre><p>より実践的な課題として、GKEにデプロイした場合の設定方法については下記のドキュメントを参照してください。</p><p><a href="https://cloud.google.com/kubernetes-engine/docs/tutorials/http-balancer?hl=ja#step_5_optional_configuring_a_static_ip_address" target="_blank" rel="noopener">Ingress での HTTP 負荷分散の設定 | Kubernetes Engine のドキュメント</a></p><h2 id="ワイルドカードDNS"><a href="#ワイルドカードDNS" class="headerlink" title="ワイルドカードDNS"></a>ワイルドカードDNS</h2><p><code>nip.io</code>というのはワイルドカードDNSと呼ばれるサービスのドメインの一つです。Exentrique Solutionsという企業によって運営されています。</p><p><a href="http://nip.io/" target="_blank" rel="noopener">http://nip.io/</a></p><p>下記のようにサブドメインに相当するIPアドレスを動的に返してくれるため、プライベートアドレスを使ったテストに便利です。</p><pre><code>$ dig +short 192.168.64.25.nip.io192.168.64.25$ dig +short www.192.168.64.25.nip.io192.168.64.25</code></pre><p>先ごろ発表された <a href="https://jenkins-x.io/" target="_blank" rel="noopener">Jenkins X</a> でも使われていました。</p><p><code>nip.io</code>はPowerDNSとカスタムスクリプトの組みわせで実装されているとのことです。<code>nip.io</code>自体はOSSではないのですが、Dockerで動作する<code>nip.io</code>クローンが存在します。</p><p><a href="https://github.com/resmo/nip.io" target="_blank" rel="noopener">https://github.com/resmo/nip.io</a></p><p>今回のようにTLSの上で通信する場合にはセキュリティリスクは限定的だと思いますが、DNSを外部に依存したくない場合には自前で<code>nip.io</code>クローンを運用するかdnsmasqなどの利用を検討してください。</p><h2 id="envsubst"><a href="#envsubst" class="headerlink" title="envsubst"></a>envsubst</h2><p><code>openssl.conf</code>や<code>puma-ing.yaml</code>では、<code>$(minikube ip)</code>に相当する部分を<code>sed</code>コマンドで環境変数に置換していました。このようにテキストファイルの一部を環境変数で置換する場合には <code>envsubst</code> というコマンドを使うとさらに簡単です。</p><p>macOSの場合はHomebrewでgettextパッケージでインストールできますが、パスを通すためには<code>brew link</code>に<code>--force</code>オプションをつけて実行する必要があります。</p><pre><code>$ brew install gettext$ brew link gettext --force</code></pre><p>今回の場合は置換の対象が一つと少なく<code>sed</code>での代替が容易だったので、手順全体の依存を減らすためにあえて採用しませんでしたが、マニフェストを全体をテンプレート化して多数のパラメータを注入できるようにしたいのであれば、採用を検討しても良いかもしれません。</p><p>ただし、そのような目的のためには次回紹介する<a href="https://helm.sh/" target="_blank" rel="noopener">Helm</a>を使う方がおすすめです。例えば<code>Secret</code>のデータエントリはBase64でエンコードする必要がありますが、<code>envsubst</code>でそれを実現しようとすると煩雑な手順になりますが、Helmであれば簡単です。</p><h1 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h1><p>下記のオブジェクトについての最低限の説明と、これらを使って<code>puma</code>、<code>sidekiq</code>、<code>mysql</code>、<code>redis</code>のコンテナをk8s上で管理するための実例を示しました。</p><ul><li><code>Deployment</code></li><li><code>Service</code></li><li><code>ConfigMap</code></li><li><code>Secret</code></li><li><code>Job</code></li><li><code>PersistentVolumeClaim</code></li><li><code>Ingress</code></li></ul><p>k8sに関しては、次のステップとして下記について調べるのが良いと思います。</p><ul><li><code>ConfigMap</code>や<code>Secret</code>の内容をボリュームとして<code>Pod</code>にマウントする方法(MySQLやRedisの設定ファイルの管理など)</li><li><code>CronJob</code>オブジェクトによる定期実行ジョブの定義方法(DBのバックアップなど)</li><li>CPUやメモリの上限の設定方法</li></ul><p>なお、今回はYAMLファイルとしてマニフェストを管理する方法を示しましたが、この構成においては下記の課題があります。</p><ul><li>ステージング環境やQA環境など、一部だけ設定を変更した環境を作るのが難しい</li><li><code>Secret</code>の定義ファイルにBase64エンコードした値を読み書きするのが面倒</li></ul><p>これらの課題に対するアプローチとして、<a href="/blog/2018/05/30/helm-with-rails/">次回</a>は <a href="https://helm.sh/" target="_blank" rel="noopener">Helm</a> を使う方法を紹介します。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。
この記事はシリーズ連載記事の第五回です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一回 &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/&quot;&gt;Docker編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第二回 &lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第三回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-tutorial/&quot;&gt;Kubernetes入門編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第四回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-with-rails/&quot;&gt;Kubernetes基礎編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第五回 &lt;a href=&quot;/blog/2018/05/30/practical-kubernetes-with-rails/&quot;&gt;Kubernetes応用編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第六回 &lt;a href=&quot;/blog/2018/05/30/helm-with-rails/&quot;&gt;Helm編&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前回の&lt;a href=&quot;/blog/2018/05/30/kubernetes-with-rails/&quot;&gt;Kubernetes基礎編&lt;/a&gt;では、
Step1として第2回&lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/#docker-composeコマンドによるローカルプレビュー環境&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt;
の&lt;code&gt;docker-compose-preview.yml&lt;/code&gt;に相当する構成を&lt;code&gt;Deployment&lt;/code&gt;, &lt;code&gt;Service&lt;/code&gt;, &lt;code&gt;ConfigMap&lt;/code&gt;, &lt;code&gt;Secret&lt;/code&gt;の4種のAPIオブジェクトで記述しました。
マニフェストファイル一式はサンプルコードの &lt;a href=&quot;https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step1/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;k8s/manifests-step1/&lt;/a&gt; ディレクトリにあります。&lt;/p&gt;
&lt;p&gt;この構成には下記に挙げる三つの制約があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pumaコンテナを複数起動すると&lt;code&gt;rails db:setup&lt;/code&gt;が並列実行されてエラーになる。&lt;/li&gt;
&lt;li&gt;MySQLやRedisのデータが永続化されていないため、コンテナを停止するとデータも消える。&lt;/li&gt;
&lt;li&gt;pumaに外部からアクセスするためのエンドポイントを本番環境で運用するのが難しい。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回はこれらの制約をStep2からStep4で解消していきます。&lt;/p&gt;
&lt;p&gt;サンプルコードは全て下記のリポジトリにあります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kwhrtsk/rails-k8s-demoapp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kwhrtsk/rails-k8s-demoapp&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;前提&quot;&gt;&lt;a href=&quot;#前提&quot; class=&quot;headerlink&quot; title=&quot;前提&quot;&gt;&lt;/a&gt;前提&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;macOSでの作業を前提としています。&lt;/li&gt;
&lt;li&gt;使用したツールのバージョンなどは &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/#前提&quot;&gt;初回&lt;/a&gt; の記事を参照してください。&lt;/li&gt;
&lt;li&gt;ツールのインストール手順は &lt;a href=&quot;/blog/2018/05/30/kubernetes-tutorial/#開発環境の構築&quot;&gt;第三回&lt;/a&gt; の記事を参照してください。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;準備&quot;&gt;&lt;a href=&quot;#準備&quot; class=&quot;headerlink&quot; title=&quot;準備&quot;&gt;&lt;/a&gt;準備&lt;/h1&gt;&lt;p&gt;まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。
(前回と同じなのですでにやっている人は読み飛ばしてください)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# サンプルアプリのコードをチェックアウト
$ 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
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;Step2-Job&quot;&gt;&lt;a href=&quot;#Step2-Job&quot; class=&quot;headerlink&quot; title=&quot;Step2: Job&quot;&gt;&lt;/a&gt;Step2: Job&lt;/h1&gt;&lt;p&gt;サンプルコードは全て下記のディレクトリにあります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step2/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;k8s/manifests-step2/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Step1の構成ではpumaのレプリカの数を1に指定していましたが、
一定以上の性能を出そうとするとレプリカの数は2以上に設定する必要があります。
ところが、このままの構成でpumaコンテナを複数起動すると、&lt;code&gt;rails db:setup&lt;/code&gt;が並列実行されてエラーが発生します。&lt;/p&gt;
&lt;p&gt;次のような手順でこれを確認できます。&lt;/p&gt;
    
    </summary>
    
      <category term="HowTo" scheme="https://chopschips.net/categories/HowTo/"/>
    
    
      <category term="rails" scheme="https://chopschips.net/tags/rails/"/>
    
      <category term="docker" scheme="https://chopschips.net/tags/docker/"/>
    
      <category term="kubernetes" scheme="https://chopschips.net/tags/kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>Railsアプリ開発のためのDocker/Kubernetes入門4 Kubernetes基礎編</title>
    <link href="https://chopschips.net/blog/2018/05/30/kubernetes-with-rails/"/>
    <id>https://chopschips.net/blog/2018/05/30/kubernetes-with-rails/</id>
    <published>2018-05-30T13:30:00.000Z</published>
    <updated>2018-05-30T16:10:13.435Z</updated>
    
    <content type="html"><![CDATA[<p>RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。この記事はシリーズ連載記事の第四回です。</p><ul><li>第一回 <a href="/blog/2018/05/30/docker-with-rails/">Docker編</a></li><li>第二回 <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a></li><li>第三回 <a href="/blog/2018/05/30/kubernetes-tutorial/">Kubernetes入門編</a></li><li>第四回 <a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes基礎編</a></li><li>第五回 <a href="/blog/2018/05/30/practical-kubernetes-with-rails/">Kubernetes応用編</a></li><li>第六回 <a href="/blog/2018/05/30/helm-with-rails/">Helm編</a></li></ul><p>今回は以下のサンプルアプリケーションをminikubeにデプロイするためのマニフェストや手順について説明します。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp" target="_blank" rel="noopener">https://github.com/kwhrtsk/rails-k8s-demoapp</a></p><h1 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h1><ul><li>macOSでの作業を前提としています。</li><li>使用したツールのバージョンなどは <a href="/blog/2018/05/30/docker-with-rails/#前提">初回</a> の記事を参照してください。</li><li>ツールのインストール手順は <a href="/blog/2018/05/30/kubernetes-tutorial/#開発環境の構築">前回</a> の記事を参照してください。</li></ul><h1 id="準備"><a href="#準備" class="headerlink" title="準備"></a>準備</h1><p>まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。(前回と同じなのですでにやっている人は読み飛ばしてください)</p><pre><code># サンプルアプリのコードをチェックアウト$ 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</code></pre><h1 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h1><p>前回は下記について説明しました。</p><ul><li>APIオブジェクトを定義するマニフェストファイルをYAML形式で記述する方法</li><li>マニフェストファイルと<code>kubectl</code>コマンドを使ってAPIオブジェクトを管理する方法</li></ul><p>今回は実際にRailsアプリをMySQLやRedisなどのミドルウェアも含めて丸ごとk8sクラスタ上にデプロイするための手順を説明します。一度に多種のAPIオブジェクトを使うと全体を理解するのが難しくなるので、4つのステップに分けて進めます。</p><p>Step1では、第2回<a href="/blog/2018/05/30/docker-compose-with-rails/#docker-composeコマンドによるローカルプレビュー環境">Docker Compose/Dockerfile編</a>の<code>docker-compose-preview.yml</code>に相当する構成をできるだけ少ないAPIオブジェクトで簡潔に記述します。この時点では簡潔さと引き換えにいくつかの制約がありますが、それらをStep2からStep4で解消していきます。</p><p>今回の<a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes基礎編</a>ではStep1を説明し、次回の<a href="/blog/2018/05/30/practical-kubernetes-with-rails/">Kubernetes応用編</a>でStep2以降を説明します。</p><a id="more"></a><h1 id="Railsアプリのイメージをminikube上で使えるようにする方法"><a href="#Railsアプリのイメージをminikube上で使えるようにする方法" class="headerlink" title="Railsアプリのイメージをminikube上で使えるようにする方法"></a>Railsアプリのイメージをminikube上で使えるようにする方法</h1><p>マニフェストの説明に入る前に、ビルドしたRailsアプリのイメージをminikube上にデプロイできるようにする方法を説明します。</p><p>いくつかの方法があります。</p><ol><li>Docker Hubのようなパブリックレジストリに登録する。</li><li><a href="https://aws.amazon.com/jp/ecr/" target="_blank" rel="noopener">AWSのECR</a>や<a href="https://cloud.google.com/container-registry/?hl=ja" target="_blank" rel="noopener">GCPのContainer Registry</a>のようなプライベートレジストリサービスに登録する。</li><li>プライベートレジストリサービスを自前で用意してそこに登録する。</li><li>minikube VM上のDockerプロセス上に接続して直接イメージをビルドする。</li></ol><p>今回は一番簡単な4の方法を使います。</p><p><a href="#準備">準備</a> の節を参照して、サンプルコードをチェックアウトしminikubeを起動したら、下記のコマンドを実行してください。</p><pre><code>$ (eval $(minikube docker-env) &amp;&amp; docker build . -t demoapp:0.0.1)</code></pre><p><code>minikube docker-env</code>コマンドは、minikube VM上のDockerプロセスに接続できるようにするための環境変数を出力します。上記のようにすることでminikube VM上のDockerプロセス上で直接イメージをビルドします。これにより、マニフェストではイメージ名を<code>demoapp:0.0.1</code>と指定すればこのイメージを使えるようになります。</p><p>この方法を使う場合、タグには<code>latest</code>以外の値を指定してください。k8sの仕様で、タグが<code>latest</code>になっているイメージを指定するとコンテナの起動前に必ずレジストリからイメージをpullしようとするため、4の方法だと必ず失敗するようになってしまいます。</p><p>これは4の方法を使う場合の制約ではあるのですが、インフラの構成管理という観点ではそもそもイメージのタグに<code>latest</code>を使うとデプロイされるバージョンがタイミング依存になるため、4以外の方法でも<code>latest</code>タグの使用は避けたほうが良いと思います。</p><h1 id="Step1-Deployment-Service-ConfigMap-Secret"><a href="#Step1-Deployment-Service-ConfigMap-Secret" class="headerlink" title="Step1: Deployment, Service, ConfigMap, Secret"></a>Step1: Deployment, Service, ConfigMap, Secret</h1><p>Step1では、第2回<a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a>のComposeファイル<code>docker-compose-preview.yml</code>に相当する構成を(いくつかの制約と引き換えに)できるだけ簡潔に記述します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># docker-compose-preview.yml</span><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span><span class="token key atrule">services</span><span class="token punctuation">:</span>  <span class="token key atrule">puma</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">build</span><span class="token punctuation">:</span>      <span class="token key atrule">context</span><span class="token punctuation">:</span> .    <span class="token key atrule">env_file</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> .dockerenv/rails    <span class="token key atrule">ports</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> 3000<span class="token punctuation">:</span><span class="token number">3000</span>    <span class="token key atrule">depends_on</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> mysql      <span class="token punctuation">-</span> redis    <span class="token key atrule">command</span><span class="token punctuation">:</span> ./bin/setup<span class="token punctuation">-</span>db<span class="token punctuation">-</span>and<span class="token punctuation">-</span>start<span class="token punctuation">-</span>puma  <span class="token key atrule">sidekiq</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">env_file</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> .dockerenv/rails    <span class="token key atrule">depends_on</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> puma    <span class="token key atrule">command</span><span class="token punctuation">:</span> ./bin/start<span class="token punctuation">-</span>sidekiq  <span class="token key atrule">mysql</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.21    <span class="token key atrule">env_file</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> .dockerenv/mysql  <span class="token key atrule">redis</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> redis<span class="token punctuation">:</span>4.0.9    <span class="token key atrule">command</span><span class="token punctuation">:</span> redis<span class="token punctuation">-</span>server <span class="token punctuation">-</span><span class="token punctuation">-</span>appendonly yes<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>このComposeファイルの要点は下記の通りです。</p><ul><li>puma, sidekiq, mysql, redisの4つのサービス(コンテナ)を起動</li><li>puma, sidekiqコンテナに設定する環境変数は <code>.dockerenv/rails</code> にまとめて記述</li><li>mysqlコンテナに設定する環境変数は <code>.dockerenv/mysql</code> にまとめて記述</li><li>pumaコンテナには3000番ポートで接続可能</li><li>データベーススキーマの初期化(<code>rails db:setup</code>)はpumaコンテナの起動時に実行<ul><li>詳細は<a href="/blog/2018/05/30/docker-compose-with-rails/">前回の記事</a>の<code>./bin/setup-db-and-start-puma</code>の解説を参照</li></ul></li></ul><p>これと同等の構成をk8sで実現するために、<code>Deployment</code>、<code>Service</code>、<code>ConfigMap</code>、<code>Secret</code>の4種類のAPIオブジェクトを使います。</p><p><code>Deployment</code>は、<code>Pod</code>(≒コンテナ)の起動管理を行うオブジェクトです。<code>Service</code>は、<code>Pod</code>へアクセスするためのI/Fを提供します。この二つは前回の<a href="/blog/2018/05/30/kubernetes-tutorial/">Kubernetes入門編</a>で解説したので詳細はそちらを参照してください。</p><p><code>Deployment</code>と<code>Service</code>の組み合わせで、Docker Composeにおける<code>service</code>に相当する機能になります。今回の例だと、<code>mysql</code>, <code>redis</code>, <code>puma</code>のサービスには外部のコンテナまたはクラスタの外部（ブラウザ）からアクセスする必要があるため、<code>Deployment</code>と<code>Service</code>を一組ずつ定義します。<code>sidekiq</code>は外から参照する必要がないため、<code>Deployment</code>のみ定義します。</p><p><code>ConfigMap</code>と<code>Secret</code>は、<code>Deployment</code>に設定する環境変数の管理に使います。今回の使い方ではComposeファイルにおける<code>.dockerenv/mysql</code>と<code>.dockerenv/rails</code>に相当します。</p><p>使用するマニフェストファイルは以下の11個で、全て<a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step1/" target="_blank" rel="noopener">k8s/manifests-step1/</a>に置いてあります。一つのYAMLファイルに一つのAPIオブジェクトの定義を書いています。</p><ul><li>mysql-deploy.yaml</li><li>mysql-env-cm.yaml</li><li>mysql-env-secret.yaml</li><li>mysql-svc.yaml</li><li>redis-deploy.yaml</li><li>redis-svc.yaml</li><li>puma-deploy.yaml</li><li>puma-svc.yaml</li><li>rails-env-cm.yaml</li><li>rails-env-secret.yaml</li><li>sidekiq-deploy.yaml</li></ul><p>これから順に内容を説明していきますが、まずはデプロイを実行してみましょう。</p><p><a href="#準備">準備</a> の節を参照して、サンプルコードをチェックアウトしminikubeを起動したら、下記のコマンドを実行してください。デプロイが済んだ後、ブラウザでサンプルアプリを開くことができます。</p><pre><code># Railsアプリのイメージをビルド$ (eval $(minikube docker-env) &amp;&amp; docker build . -t demoapp:0.0.1)$ cd k8s/manifests-step1# APIオブジェクトを作成$ cat *.yaml | kubectl apply -f -# pumaとsidekiqの起動が完了するまで待機$ kubectl rollout status deploy demoapp-puma$ kubectl rollout status deploy demoapp-sidekiq# puma serviceのエンドポイントをブラウザでオープン$ minikube service demoapp-puma</code></pre><p>上記の手順と等価なタスクをMakefileに定義してあるので、代わりに<code>make</code>コマンドで実行することもできます。</p><pre><code>$ cd k8s/manifests-step1$ make kubectl-apply$ make kubectl-rollout-status$ make minikube-service# またはパラメータなしの make で全て順に実行</code></pre><p>APIオブジェクトを削除するには次のようにします。</p><pre><code>$ cd k8s/manifests-step1$ cat *.yaml | kubectl delete -f -# または make clean</code></pre><p>Makefileは下記のような内容です。GNU Makeで動作を確認しています。</p><pre class="line-numbers language-makefile"><code class="language-makefile"><span class="token comment" spellcheck="true"># k8s/manifests-step1/Makefile</span>SHELL <span class="token operator">=</span> /bin/bash<span class="token symbol">all</span><span class="token punctuation">:</span>    <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> minikube-docker-build    <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> kubectl-apply    <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> kubectl-rollout-status    <span class="token variable">$</span><span class="token punctuation">(</span>MAKE<span class="token punctuation">)</span> minikube-service<span class="token symbol">clean</span><span class="token punctuation">:</span> kubectl-delete<span class="token symbol">minikube-docker-build</span><span class="token punctuation">:</span><span class="token symbol">    eval <span class="token variable">$$</span>(minikube docker-env) &amp;&amp; docker build ../../ -t demoapp</span><span class="token punctuation">:</span>0.0.1<span class="token symbol">kubectl-apply</span><span class="token punctuation">:</span>    cat *.yaml <span class="token operator">|</span> kubectl apply -f -<span class="token symbol">kubectl-rollout-status</span><span class="token punctuation">:</span>    kubectl rollout status deploy demoapp-puma    kubectl rollout status deploy demoapp-sidekiq<span class="token symbol">minikube-service</span><span class="token punctuation">:</span>    minikube service demoapp-puma<span class="token symbol">kubectl-delete</span><span class="token punctuation">:</span>    cat *.yaml <span class="token operator">|</span> kubectl delete -f -<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>スクリプト化するまでもないような短いコマンドであっても、定型的な処理はこのように形として残しておくのがおすすめです。こういった小さな積み重ねがチーム内に暗黙知が生まれることを防ぎます。</p><p>次に、各マニフェストファイルの内容を説明します。</p><h2 id="mysql-deploy-yaml-mysql-の-Deployment"><a href="#mysql-deploy-yaml-mysql-の-Deployment" class="headerlink" title="mysql-deploy.yaml: mysql の Deployment"></a>mysql-deploy.yaml: mysql の Deployment</h2><p>はじめにMySQLに関連するマニフェストの内容を説明します。</p><p>まずは <code>Deployment</code> から。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/mysql-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> mysql<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">1</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp      <span class="token key atrule">component</span><span class="token punctuation">:</span> mysql  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">labels</span><span class="token punctuation">:</span>        <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp        <span class="token key atrule">component</span><span class="token punctuation">:</span> mysql    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> mysql          <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.21          <span class="token key atrule">livenessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">tcpSocket</span><span class="token punctuation">:</span>              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3306</span>          <span class="token key atrule">readinessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">tcpSocket</span><span class="token punctuation">:</span>              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3306</span>          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql<span class="token punctuation">-</span>env            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql<span class="token punctuation">-</span>env<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>最初なので全般事項についてもここで説明します。</p><ul><li><code>.metadata.name</code>には<code>demoapp-mysql</code>のように<code>demoapp-</code>というプレフィクスをつけます。例えば、<code>Service</code>オブジェクトの名前を<code>redis</code>にしてしまうと、それ以降に起動する全てのコンテナにk8sによって<code>REDIS_PORT</code>のような環境変数が設定されるのですが、sidekiqなど一部のgemはこういった環境変数によって動作が変わってしまう場合があります。名前のプレフィクスはこのような環境変数とgemなどで一般的に使用される環境変数が競合するのを避けるのが目的です。自動設定される環境変数の詳細については<a href="https://kubernetes.io/docs/concepts/services-networking/service/#environment-variables" target="_blank" rel="noopener">ドキュメント</a>を参照してください。</li><li>各種ラベルには、共通で<code>app</code>ラベルに<code>demoapp</code>を、コンポーネントごとに<code>component</code>ラベルを設定します。コンポーネントは<code>puma</code>, <code>sidekiq</code>, <code>mysql</code>, <code>redis</code>などです。</li><li>同一のコンポーネントに属するAPIオブジェクトには、同一の名前とラベルを設定します。例えば、<code>mysql</code>用の<code>Deployment</code>、<code>Service</code>には<code>.metadata.name</code>に共通の<code>demoapp-mysql</code>という名前をつけます。また、<code>.metadata.labels</code>には<code>app: demoapp</code>, <code>component: mysql</code>の二つを設定します。</li><li>マニフェストのファイル名には、コンポーネントとAPIオブジェクトの種類の略称を使います。例えば <code>mysql</code> の <code>Deployment</code>であれば <code>mysql-deploy.yaml</code> となります。略称については <a href="/blog/2018/05/30/kubernetes-tutorial/#kubectl-get-APIオブジェクトの状態の確認">前回の<code>kubectl get</code>の節</a>を参照してください。</li></ul><p>次に、<code>mysql</code>のための固有の設定について説明します。</p><p><code>livenessProbe</code>と<code>readinessProbe</code>は、コンテナの死活監視のための設定項目です。この例では3306番ポートにTCP接続できるかどうかで判定を行なっています。また、上記では使用していませんが、それぞれ<code>initialDelaySeconds</code>(最初に検査を行うまでの待機秒数)や<code>periodSeconds</code>(検査の間隔: デフォルト10秒)などの項目を設定することができます(<a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes" target="_blank" rel="noopener">詳細</a>)。また、コマンドを実行したりHTTPリクエストを投げることもできます。</p><p><code>livenessProbe</code>で死亡判定されると、<code>ReplicaSet</code>は<code>restartPolicy</code>に基づいてその<code>Pod</code>の再起動か再作成を試みます。条件が厳しすぎるとプロセスの起動処理中に強制的に再起動されていつまで経っても起動しなくなります。その場合は<code>initialDelaySeconds</code>などの値を緩めるなどして調整する必要があります。</p><p><code>readinessProbe</code>は<code>Service</code>オブジェクトがその<code>Pod</code>に接続リクエストを転送するかどうかの判定に使います。こちらは起動直後の<code>Pod</code>にリクエストを転送するのを防ぐことなどを目的に使用します。</p><p><code>envFrom</code>は環境変数を設定するための項目です。この例では <code>ConfigMap</code> と <code>Secret</code> という別のAPIオブジェクトを参照して環境変数を設定しています。<code>ConfigMap</code>と<code>Secret</code>については後述します。</p><p><code>env</code>を使うと直接<code>Deployment</code>の定義の内部に環境変数を定義することもできます。詳細は下記を参照してください。</p><p><a href="https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/" target="_blank" rel="noopener">Define Environment Variables for a Container | Kubernetes</a></p><h2 id="mysql-svc-yaml-mysql-の-Service"><a href="#mysql-svc-yaml-mysql-の-Service" class="headerlink" title="mysql-svc.yaml: mysql の Service"></a>mysql-svc.yaml: mysql の Service</h2><p>続いて<code>mysql-service.yaml</code>の内容を説明します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/mysql-svc.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Service<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> mysql<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">ports</span><span class="token punctuation">:</span>    <span class="token punctuation">-</span> <span class="token key atrule">protocol</span><span class="token punctuation">:</span> TCP      <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3306</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> mysql<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>.spec.selector</code>に<code>app</code>と<code>component</code>という二つのラベルを指定しています。これは<code>mysql-deploy.yaml</code>の<code>.spec.template.metadata.labels</code>と一致させる必要があります。</p><p>前回の<a href="/blog/2018/05/30/kubernetes-tutorial/">Kubernetes入門編</a>では<code>app</code>というラベル一つで<code>Deployment</code>と<code>Service</code>を接続していましたが、今回は<code>Deployment</code>が<code>mysql</code>の他に<code>redis</code>や<code>puma</code>など複数存在するため、<code>app: demoapp</code>というラベルだけだと<code>mysql</code>の<code>Service</code>が<code>redis</code>や<code>puma</code>の<code>Pod</code>を参照して混線することになります。これを避けるために<code>app</code>と<code>component</code>という二つのラベルをセレクタに指定しています。</p><h2 id="mysql-env-cm-yaml-mysqlの環境変数用ConfigMap"><a href="#mysql-env-cm-yaml-mysqlの環境変数用ConfigMap" class="headerlink" title="mysql-env-cm.yaml: mysqlの環境変数用ConfigMap"></a>mysql-env-cm.yaml: mysqlの環境変数用ConfigMap</h2><p>次に<code>mysql-env-cm.yaml</code>を説明します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/mysql-env-cm.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> ConfigMap<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql<span class="token punctuation">-</span>env<span class="token key atrule">data</span><span class="token punctuation">:</span>  <span class="token key atrule">MYSQL_USER</span><span class="token punctuation">:</span> demoapp  <span class="token key atrule">MYSQL_DATABASE</span><span class="token punctuation">:</span> demoapp_production<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>ConfigMap</code>は環境変数や設定ファイルなどアプリケーションのパラメータを管理するためのAPIオブジェクトです。使用形態には大まかに3つの方法があります。</p><ol><li><code>envFrom</code>で丸ごと環境変数に設定する。</li><li><code>env</code>でキーを指定して個別に環境変数に設定する。</li><li>キーを指定して値をファイルとして<code>Pod</code>上のファイルにマウントする。</li></ol><p><code>mysql-deploy.yaml</code>では1の方法を使っています。関係ある部分だけ抜き出すと下記の通りです。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/mysql-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> mysql          <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.21          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql<span class="token punctuation">-</span>env <span class="token comment" spellcheck="true"># ConfigMapの `.metadata.name` を指定</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>このように参照すると、ConfigMapの<code>.data</code>の各エントリが全て環境変数として設定されます。</p><p>2の方法で同じことをしようとすると下記のようになります。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/mysql-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> mysql          <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.21          <span class="token key atrule">env</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> MYSQL_USER  <span class="token comment" spellcheck="true"># 環境変数名を指定</span>              <span class="token key atrule">valueFrom</span><span class="token punctuation">:</span>                <span class="token key atrule">configMapKeyRef</span><span class="token punctuation">:</span>                  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql<span class="token punctuation">-</span>env <span class="token comment" spellcheck="true"># ConfigMapの`.metadata.name`を指定</span>                  <span class="token key atrule">key</span><span class="token punctuation">:</span> MYSQL_USER         <span class="token comment" spellcheck="true"># ConfigMapの`.data`エントリのキーを指定</span>            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> MYSQL_DATABASE              <span class="token key atrule">valueFrom</span><span class="token punctuation">:</span>                <span class="token key atrule">configMapKeyRef</span><span class="token punctuation">:</span>                  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql<span class="token punctuation">-</span>env                  <span class="token key atrule">key</span><span class="token punctuation">:</span> DATABASE<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>だいぶ冗長になりました。<code>envFrom</code>は比較的最近追加された機能なので以前はこの方法で書いていましたが大変でした。<code>ConfigMap</code>はできるだけ用途に応じた単位で分割して、<code>envFrom</code>を使えるようにした方が良いと思います。</p><p>3の方法は、例えば<code>/etc/mysql/my.cnf</code>のような設定ファイルを<code>ConfigMap</code>で管理する方法です。<code>.data</code>エントリに定義した値をファイルとして<code>Pod</code>上の任意のパスにマウントすることができます。この方法についてはこのドキュメントでは扱いませんので、詳細は下記の資料を参照してください。</p><p><a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#add-configmap-data-to-a-volume" target="_blank" rel="noopener">Configure a Pod to Use a ConfigMap | Kubernetes</a></p><p><code>ConfigMap</code>と<code>Secret</code>については、APIオブジェクトの名前(<code>.metadata.name</code>)を<code>${プレフィクス}-${コンポーネント}</code>の形式ではなく、さらに<code>-env</code>を付け加えて<code>demoapp-mysql-env</code>のような形にしています。これは将来的に環境変数に加えて3の方法で設定ファイルを追加する場合に名前が競合するのを防ぐ意図があります。(設定ファイル用の<code>ConfigMap</code>を追加する場合は<code>demoapp-mysql-conf</code>のような名前にするのが良いと思います)</p><ul><li><a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/" target="_blank" rel="noopener">Configure a Pod to Use a ConfigMap | Kubernetes</a></li></ul><h2 id="mysql-env-secret-yaml-mysqlの環境変数用Secret"><a href="#mysql-env-secret-yaml-mysqlの環境変数用Secret" class="headerlink" title="mysql-env-secret.yaml: mysqlの環境変数用Secret"></a>mysql-env-secret.yaml: mysqlの環境変数用Secret</h2><p>最後に<code>mysql-env-secret.yaml</code>を説明します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/mysql-env-secret.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Secret<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>mysql<span class="token punctuation">-</span>env<span class="token key atrule">data</span><span class="token punctuation">:</span>  <span class="token key atrule">MYSQL_PASSWORD</span><span class="token punctuation">:</span> c2VjcmV0 <span class="token comment" spellcheck="true"># echo -n "secret" | base64</span>  <span class="token key atrule">MYSQL_ROOT_PASSWORD</span><span class="token punctuation">:</span> dG9wc2VjcmV0 <span class="token comment" spellcheck="true"># echo -n "topsecret" | base64</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>Secret</code>は<code>ConfigMap</code>同様キー・バリュー形式で設定値を管理できるAPIオブジェクトですが、パスワードやクレデンシャルなど機密性の高い情報を管理することを前提としています。</p><p><code>Secret</code>は、<code>ConfigMap</code>と比較すると表面的には下記の点が異なります。</p><ul><li><code>.data</code>エントリに値を書き込むときはBASE64エンコードして書く必要がある。</li><li>Dashboard上ではデフォルトで値が表示されないようになっている。(クリックで表示させることはできる)<ul><li>GCPのConsole上だと表示できない</li></ul></li></ul><p>また、<code>Secret</code>の中身は<code>Pod</code>のtmpfs上に展開されディスクには書き込まれないなど、内部的には<code>ConfigMap</code>よりも安全性に配慮された運用が行われます。セキュリティ上の制限については下記のドキュメントを参照してください。</p><p><a href="https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/" target="_blank" rel="noopener">Encrypting Secret Data at Rest | Kubernetes</a></p><p><code>Deployment</code>からの参照方法はほとんど<code>ConfigMap</code>と同じなので説明を割愛します。<code>configMapRef</code>の代わりに<code>secretRef</code>を使う必要がある点にだけ注意してください。</p><p>なお、<code>Secret</code>オブジェクトのマニフェストファイルにはBASE64エンコードしただけの値を書いているので平文で機密情報を書いているのと同じです。通常はこのままgitリポジトリにコミットすることはしません。実際の運用の際には <a href="https://github.com/mozilla/sops#extract-a-sub-part-of-a-document-tree" target="_blank" rel="noopener">sops</a>や<a href="https://github.com/joker1007/yaml_vault" target="_blank" rel="noopener">yaml_vault</a> とGCP/AWSのKMSで暗号化するのがおすすめですが、それについてはHelm編で紹介したいと思います。</p><h2 id="redis-deploy-yaml-redis-の-Deployment"><a href="#redis-deploy-yaml-redis-の-Deployment" class="headerlink" title="redis-deploy.yaml: redis の Deployment"></a>redis-deploy.yaml: redis の Deployment</h2><p>次にRedisに関連するマニフェストの内容を説明します。</p><p>まずは <code>Deployment</code> から。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/redis-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>redis  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> redis<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">1</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp      <span class="token key atrule">component</span><span class="token punctuation">:</span> redis  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">labels</span><span class="token punctuation">:</span>        <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp        <span class="token key atrule">component</span><span class="token punctuation">:</span> redis    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> redis          <span class="token key atrule">image</span><span class="token punctuation">:</span> redis<span class="token punctuation">:</span>4.0.9          <span class="token key atrule">livenessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">tcpSocket</span><span class="token punctuation">:</span>              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379</span>          <span class="token key atrule">readinessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">tcpSocket</span><span class="token punctuation">:</span>              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379</span>          <span class="token key atrule">command</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> redis<span class="token punctuation">-</span>server            <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>appendonly            <span class="token punctuation">-</span> <span class="token string">"yes"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>構造としては <code>mysql-deploy.yaml</code> とほとんど同じです。</p><p><code>command</code>に関しては、<code>docker-compose-preview.yaml</code>では下記のように文字列のエントリとして定義していましたが、k8sのマニフェストでは上記の通り配列として定義する必要がある点に注意してください。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">services</span><span class="token punctuation">:</span>  <span class="token key atrule">redis</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> redis<span class="token punctuation">:</span>4.0.9    <span class="token key atrule">command</span><span class="token punctuation">:</span> redis<span class="token punctuation">-</span>server <span class="token punctuation">-</span><span class="token punctuation">-</span>appendonly yes<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h2 id="redis-svc-yaml-redis-の-Service"><a href="#redis-svc-yaml-redis-の-Service" class="headerlink" title="redis-svc.yaml: redis の Service"></a>redis-svc.yaml: redis の Service</h2><p>続いて <code>Service</code> の内容を確認します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/redis-svc.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Service<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>redis  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> redis<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">ports</span><span class="token punctuation">:</span>    <span class="token punctuation">-</span> <span class="token key atrule">protocol</span><span class="token punctuation">:</span> TCP      <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> redis<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>component</code>とポート番号が違うだけで<code>mysql</code>のものとほとんど同じです。特筆すべき点はありません。</p><h2 id="puma-deploy-yaml-puma-の-Deployment"><a href="#puma-deploy-yaml-puma-の-Deployment" class="headerlink" title="puma-deploy.yaml: puma の Deployment"></a>puma-deploy.yaml: puma の Deployment</h2><p>いよいよRailsアプリ本体のためのマニフェストの説明に移ります。</p><p>まずは<code>puma</code>用の<code>Deployment</code>から説明します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/puma-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> puma<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">1</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp      <span class="token key atrule">component</span><span class="token punctuation">:</span> puma  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">labels</span><span class="token punctuation">:</span>        <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp        <span class="token key atrule">component</span><span class="token punctuation">:</span> puma    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> puma          <span class="token key atrule">image</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">:</span>0.0.1          <span class="token key atrule">imagePullPolicy</span><span class="token punctuation">:</span> IfNotPresent          <span class="token key atrule">command</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> ./bin/setup<span class="token punctuation">-</span>db<span class="token punctuation">-</span>and<span class="token punctuation">-</span>start<span class="token punctuation">-</span>puma          <span class="token key atrule">livenessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">httpGet</span><span class="token punctuation">:</span>              <span class="token key atrule">path</span><span class="token punctuation">:</span> /health_check/full              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>            <span class="token key atrule">initialDelaySeconds</span><span class="token punctuation">:</span> <span class="token number">30</span>          <span class="token key atrule">readinessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">httpGet</span><span class="token punctuation">:</span>              <span class="token key atrule">path</span><span class="token punctuation">:</span> /health_check/full              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>            <span class="token key atrule">initialDelaySeconds</span><span class="token punctuation">:</span> <span class="token number">30</span>          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>image</code>には<a href="#Railsアプリのイメージをminikube上で使えるようにする方法">docker build</a>コマンドで指定したイメージ名を指定します。</li><li><code>imagePullPolicy</code>は<code>IfNotPresent</code>を指定します。前述の通りこのイメージはレジストリに登録していないためです。</li><li><code>command</code>には<code>docker-compose-preview.yml</code>と同じく<code>./bin/setup-db-and-start-puma</code>を指定します。これはpumaの起動前に<code>rails db:setup</code>を試みるスクリプトです。詳細は<a href="/blog/2018/05/30/docker-compose-with-rails/">第2回の記事</a>で説明しました。</li><li><code>livenessProbe</code>と<code>readinessProbe</code>ではそれぞれ<code>/health_check/full</code>というパスを指定しています。このサンプルアプリでは<a href="https://github.com/ianheggie/health_check" target="_blank" rel="noopener">health_check</a>というgemで死活監視用のエンドポイントを実装しています。このパスを叩くだけでMySQLやRedisとの接続検証を行うことができるようになります。</li><li><code>envFrom</code>では、<code>demoapp-rails-env</code>という名前の<code>ConfigMap</code>と<code>Secret</code>を参照しています。これらのオブジェクトは<code>docker-compose-preview.yml</code>における<code>env_file</code>に相当する環境変数の設定を保持しています。</li></ul><h2 id="puma-svc-yaml-puma-の-Service"><a href="#puma-svc-yaml-puma-の-Service" class="headerlink" title="puma-svc.yaml: puma の Service"></a>puma-svc.yaml: puma の Service</h2><p>次にpuma用の<code>Service</code>の定義を確認します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/puma-svc.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Service<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>puma  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> puma<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">type</span><span class="token punctuation">:</span> NodePort  <span class="token key atrule">ports</span><span class="token punctuation">:</span>    <span class="token punctuation">-</span> <span class="token key atrule">protocol</span><span class="token punctuation">:</span> TCP      <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> puma<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>.spec.type</code>に<code>NodePort</code>を指定しています。<code>mysql</code>や<code>redis</code>と異なり、<code>puma</code>の接続相手は他のコンテナではなくクラスタの外にあるブラウザです。そのため、クラスタ内での通信のためのエンドポイントを生成する<code>ClusterIP</code>の代わりに<code>NodePort</code>を指定しています。</p><p><code>NodePort</code>を指定すると、k8sクラスタを構成する全てのノードに、このサービスへ接続するためのエンドポイントが作成されます。<code>.ports[*].nodePort</code>で公開するポートを指定することもできますが、使用中のポートを指定するとエラーになります。無指定の場合は、<code>30000-32767</code>のレンジから自動的に割り当てられます。いずれの場合も、ポート番号は全てのノードで同じになります。</p><p><a href="https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport" target="_blank" rel="noopener">Type NodePort</a></p><p>minikubeの場合、このサービスへ接続するためのURLは<code>minikube service list</code>で確認することができます。</p><pre><code>% minikube service list|-------------|----------------------|----------------------------||  NAMESPACE  |         NAME         |            URL             ||-------------|----------------------|----------------------------|| default     | demoapp-mysql        | No node port               || default     | demoapp-puma         | http://192.168.64.25:32320 || default     | demoapp-redis        | No node port               || default     | kubernetes           | No node port               || kube-system | default-http-backend | http://192.168.64.25:30001 || kube-system | kube-dns             | No node port               || kube-system | kubernetes-dashboard | http://192.168.64.25:30000 ||-------------|----------------------|----------------------------|</code></pre><p>また、<code>minikube service demoapp-puma</code>のように<code>Service</code>の名前をパラメータとして渡せば、そのURLをブラウザでオープンすることができます。典型的には、<code>http://$(minikube ip):32320</code>のように<code>minikube ip</code>にランダムなポート番号を与えた形式のURLでアクセスできるようになります。</p><p>さて、前述のように<code>NodePort</code>を指定するとk8sクラスタを構成する全てのノードにエンドポイントが用意されます。minikubeのようにシングルノード構成の検証用クラスタではこれで十分ですが、マルチノード構成の本番環境では不十分です。少なくとも負荷分散や可用性担保のためには前段にロードバランサが必要になるし、k8sの外側でロードバランサの管理をするのは面倒です。</p><p>外向けのエンドポイントにロードバランサを組み合わせる場合、通常は<code>LoadBalancer</code>タイプの<code>Service</code>か<code>Ingress</code>というオブジェクトを使います。<a href="/blog/2018/05/30/practical-kubernetes-with-rails/#Step4-Ingress">次回Kubernetes応用編のStep4</a>では<code>Ingress</code>を使ってこの問題に対処する方法を示します。</p><h2 id="rails-env-cm-yaml-pumaとsidekiqに共通の環境変数用ConfigMap"><a href="#rails-env-cm-yaml-pumaとsidekiqに共通の環境変数用ConfigMap" class="headerlink" title="rails-env-cm.yaml: pumaとsidekiqに共通の環境変数用ConfigMap"></a>rails-env-cm.yaml: pumaとsidekiqに共通の環境変数用ConfigMap</h2><p>下記のような内容です。MySQLやRedisのホスト名として、それぞれ<code>mysql-svc.yaml</code>と<code>redis-svc.yaml</code>で定義した<code>Service</code>オブジェクトの名前を指定しています。</p><pre><code># k8s/manifests-step1/rails-env-cm.yaml---apiVersion: v1kind: ConfigMapmetadata:  name: demoapp-rails-envdata:  RAILS_SERVE_STATIC_FILES: &quot;true&quot;  RAILS_LOG_TO_STDOUT: &quot;true&quot;  SIDEKIQ_TIMEOUT: &quot;60&quot;  MYSQL_HOST: demoapp-mysql  MYSQL_USER: demoapp  MYSQL_DATABASE: demoapp_production  REDIS_HOST: demoapp-redis  REDIS_URL: redis://demoapp-redis:6379/1</code></pre><h2 id="rails-env-secret-yaml-pumaとsidekiqに共通の環境変数用Secret"><a href="#rails-env-secret-yaml-pumaとsidekiqに共通の環境変数用Secret" class="headerlink" title="rails-env-secret.yaml: pumaとsidekiqに共通の環境変数用Secret"></a>rails-env-secret.yaml: pumaとsidekiqに共通の環境変数用Secret</h2><p>特筆すべき点は特にありません。</p><p><code>mysql-env-secret.yaml</code>の説明の際にも述べたとおり、環境変数の値はこの時点では暗号化されていないため、実際の運用においては <a href="https://github.com/mozilla/sops#extract-a-sub-part-of-a-document-tree" target="_blank" rel="noopener">sops</a>や<a href="https://github.com/joker1007/yaml_vault" target="_blank" rel="noopener">yaml_vault</a> で暗号化するなどしてからgitリポジトリに含める必要があります。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/rails-env-secret.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Secret<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env<span class="token key atrule">data</span><span class="token punctuation">:</span>  <span class="token key atrule">SECRET_KEY_BASE</span><span class="token punctuation">:</span> MTIz <span class="token comment" spellcheck="true"># echo -n "123" | base64</span>  <span class="token key atrule">MYSQL_PASSWORD</span><span class="token punctuation">:</span> c2VjcmV0 <span class="token comment" spellcheck="true"># echo -n "secret" | base64</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="sidekiq-deploy-yaml-sidekiq-の-Deployment"><a href="#sidekiq-deploy-yaml-sidekiq-の-Deployment" class="headerlink" title="sidekiq-deploy.yaml: sidekiq の Deployment"></a>sidekiq-deploy.yaml: sidekiq の Deployment</h2><p>最後にsidekiq用の<code>Deployment</code>の定義を見ます。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step1/sidekiq-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>sidekiq  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">component</span><span class="token punctuation">:</span> sidekiq<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">1</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp      <span class="token key atrule">component</span><span class="token punctuation">:</span> sidekiq  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">labels</span><span class="token punctuation">:</span>        <span class="token key atrule">app</span><span class="token punctuation">:</span> demoapp        <span class="token key atrule">component</span><span class="token punctuation">:</span> sidekiq    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always      <span class="token key atrule">terminationGracePeriodSeconds</span><span class="token punctuation">:</span> <span class="token number">65</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> sidekiq          <span class="token key atrule">image</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">:</span>0.0.1          <span class="token key atrule">imagePullPolicy</span><span class="token punctuation">:</span> IfNotPresent          <span class="token key atrule">command</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> ./bin/start<span class="token punctuation">-</span>sidekiq          <span class="token key atrule">livenessProbe</span><span class="token punctuation">:</span>            <span class="token key atrule">exec</span><span class="token punctuation">:</span>              <span class="token key atrule">command</span><span class="token punctuation">:</span>                <span class="token punctuation">-</span> ./bin/health<span class="token punctuation">-</span>check<span class="token punctuation">-</span>sidekiq            <span class="token key atrule">initialDelaySeconds</span><span class="token punctuation">:</span> <span class="token number">30</span>          <span class="token key atrule">envFrom</span><span class="token punctuation">:</span>            <span class="token punctuation">-</span> <span class="token key atrule">configMapRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env            <span class="token punctuation">-</span> <span class="token key atrule">secretRef</span><span class="token punctuation">:</span>                <span class="token key atrule">name</span><span class="token punctuation">:</span> demoapp<span class="token punctuation">-</span>rails<span class="token punctuation">-</span>env<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>内容としてはほぼ<code>puma-deploy.yaml</code>と同じですが、下記の点に注目してください。</p><ul><li><code>terminationGracePeriodSeconds</code>は<code>Pod</code>にTERMシグナルを送ってからプロセスの終了を待つ時間です。この時間を超えるとプロセスはKILLシグナルで強制的に停止されます。デフォルト値は30秒です(<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#pod-v1-core" target="_blank" rel="noopener">参考</a>)。<code>./bin/start-sidekiq</code>では<code>-t $SIDEKIQ_TIMEOUT</code>オプションをつけて<code>sidekiq</code>を起動しており、<code>$SIDEKIQ_TIMEOUT</code>には<code>rails-env-cm.yaml</code>で60秒を指定しているので、<code>terminationGracePeriodSeconds</code>には65秒を設定しています。ローリングアップデートやコンテナ終了時の動作の詳細についてはStep2で説明します。</li><li><code>livenessProbe</code>は<code>puma</code>と異なり<code>exec</code>タイプで独自実装のコマンドを呼び出します。内容については後述します。</li><li><code>Service</code>と接続しないため<code>readinessProbe</code>は定義しません。</li><li><code>envFrom</code>は完全に<code>puma</code>と同じです。厳密にいうと環境変数<code>SECRET_KEY_BASE</code>は無くても動作するのですが、設定を簡素化するために<code>puma</code>と共通化しています。</li></ul><h3 id="sidekiqワーカープロセスの死活監視"><a href="#sidekiqワーカープロセスの死活監視" class="headerlink" title="sidekiqワーカープロセスの死活監視"></a>sidekiqワーカープロセスの死活監視</h3><p><code>sidekiq</code>のワーカープロセスには、<code>puma</code>のようなヘルスチェックのためのインタフェースがありません。<code>sidekiq</code>のオプションでPIDをファイルに書き出すようにすればプロセスの存在確認くらいはできるのですが、そもそもプロセスが停止しているようなケースではコンテナ自体が終了してしまうためk8s側で死亡判定できます。問題は何らかの原因で<code>sidekiq</code>プロセスが生きたまま応答しなくなっているようなケースをいかに検出するかです。</p><p><code>sidekiq</code>のワーカープロセスは定期的に自身のホスト名やプロセスIDなどの情報をRedisに書き込んでいます。この処理はheartbeatと呼ばれています。(詳細はこの<a href="https://github.com/mperham/sidekiq/blob/b2c8bc6e49707e3378044a1ba47068fc8b6154f6/lib/sidekiq/launcher.rb#L71" target="_blank" rel="noopener">素敵な絵文字メソッド</a>を参照)</p><p>heartbeatは5秒間隔で実行されますが、60秒間更新が無いと自動的にRedisから削除されるようになっています。heartbeatによってRedisに書き込まれたワーカーのリストは<code>sidekiq</code>のWeb UI上でも確認することができます。</p><img src="/images/post_20180502_kubernetes_with_rails/sidekiq-web-ui.png"><p>そしてこのワーカーのリストは、SidekiqのAPIでも取得することができます。</p><p><a href="https://github.com/mperham/sidekiq/wiki/API#processes" target="_blank" rel="noopener">https://github.com/mperham/sidekiq/wiki/API#processes</a></p><p>k8sでは<code>Pod</code>のホスト名は<code>Pod</code>の名前と同じになるのでユニークであることが保証されています。<code>livenessProbe</code>で実行しているスクリプト<code>./bin/health-check-sidekiq</code>では、このAPIを利用してワーカーのリストに自身のホスト名が含まれているかどうかを確認しています。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token shebang important">#!/bin/bash</span> -e<span class="token function">cd</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">dirname</span> $0<span class="token variable">)</span></span>/<span class="token punctuation">..</span>./bin/rake sidekiq:status<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token comment" spellcheck="true"># lib/tasks/sidekiq.rake</span>namespace <span class="token symbol">:sidekiq</span> <span class="token keyword">do</span>  desc <span class="token string">"Health check for sidekiq worker process."</span>  task status<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token symbol">:environment</span><span class="token punctuation">]</span> <span class="token keyword">do</span>    <span class="token keyword">require</span> <span class="token string">"socket"</span>    hostname <span class="token operator">=</span> <span class="token constant">Socket</span><span class="token punctuation">.</span>gethostname    <span class="token keyword">if</span> <span class="token constant">Sidekiq</span><span class="token punctuation">:</span><span class="token symbol">:ProcessSet</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">.</span>any<span class="token operator">?</span> <span class="token punctuation">{</span> <span class="token operator">|</span>ps<span class="token operator">|</span> ps<span class="token punctuation">[</span><span class="token string">"hostname"</span><span class="token punctuation">]</span> <span class="token operator">==</span> hostname <span class="token punctuation">}</span>      exit <span class="token number">0</span>    <span class="token keyword">else</span>      exit <span class="token number">1</span>    <span class="token keyword">end</span>  <span class="token keyword">end</span><span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>死活監視が意図したように動作するかどうかは、<code>sidekiq</code>プロセスに<code>SIGSTOP</code>シグナルを送ってサスペンドすれば確認できます。<code>SIGKILL</code>や<code>SIGTERM</code>などでは即座にコンテナが終了するため、<code>livenessProbe</code>を待たず<code>Pod</code>が再起動される点に注意してください。</p><pre><code>$ kubectl get podsNAME                               READY     STATUS    RESTARTS   AGEdemoapp-mysql-57d56b47cd-ztc5k     1/1       Running   0          2mdemoapp-puma-749c456c87-6vhrl      1/1       Running   0          2mdemoapp-redis-58f795f487-v2x4j     1/1       Running   0          2mdemoapp-sidekiq-669cd7cb6c-bfhpf   1/1       Running   0          2m$ kubectl exec demoapp-sidekiq-669cd7cb6c-bfhpf -- pgrep -l sidekiq31 sidekiq 5.1.3 app [0 of 25 busy]$ kubectl exec demoapp-sidekiq-669cd7cb6c-bfhpf -- pkill -STOP sidekiq</code></pre><p>この後、60秒ほど待つとSidekiqのWeb UI上でワーカープロセスが消えるのを確認できます。さらにその後、だいたい10秒に一度の頻度でヘルスチェックが失敗したというログが<code>sidekiq</code>の<code>Pod</code>のイベントログに表示されます。3回失敗したところでコンテナがKillされて再起動し、再びSidekiqのWeb UI上に新しいワーカーが現れるのを確認できます。</p><pre><code>$ kubectl get pods --watchNAME                               READY     STATUS    RESTARTS   AGEdemoapp-mysql-57d56b47cd-ztc5k     1/1       Running   0          3mdemoapp-puma-749c456c87-6vhrl      1/1       Running   0          3mdemoapp-redis-58f795f487-v2x4j     1/1       Running   0          3mdemoapp-sidekiq-669cd7cb6c-bfhpf   1/1       Running   0          3mdemoapp-sidekiq-669cd7cb6c-bfhpf   1/1       Running   1         5m$ kubectl describe pod demoapp-sidekiq-669cd7cb6c-bfhpf(省略)Events:  Type     Reason                 Age               From               Message  ----     ------                 ----              ----               -------  Normal   Scheduled              5m                default-scheduler  Successfully assigned demoapp-sidekiq-669cd7cb6c-bfhpf to minikube  Normal   SuccessfulMountVolume  5m                kubelet, minikube  MountVolume.SetUp succeeded for volume &quot;default-token-dzvgp&quot;  Warning  Unhealthy              42s (x3 over 1m)  kubelet, minikube  Liveness probe failed:  Normal   Pulled                 11s (x2 over 5m)  kubelet, minikube  Container image &quot;demoapp:0.0.1&quot; already present on machine  Normal   Created                11s (x2 over 5m)  kubelet, minikube  Created container  Normal   Started                11s (x2 over 5m)  kubelet, minikube  Started container  Normal   Killing                11s               kubelet, minikube  Killing container with id docker://sidekiq:Container failed liveness probe.. Container will be killed and recreated.</code></pre><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>Step1では、第2回<a href="/blog/2018/05/30/docker-compose-with-rails/#docker-composeコマンドによるローカルプレビュー環境">Docker Compose/Dockerfile編</a>の<code>docker-compose-preview.yml</code>に相当する構成を<code>Deployment</code>, <code>Service</code>, <code>ConfigMap</code>, <code>Secret</code>の4種のAPIオブジェクトで記述しました。</p><p>この時点では下記に挙げる三つの制約があります。</p><ul><li>pumaコンテナを複数起動すると<code>rails db:setup</code>が並列実行されてエラーになる。</li><li>MySQLやRedisのデータが永続化されていないため、コンテナを停止するとデータも消える。</li><li>pumaに外部からアクセスするためのエンドポイントを本番環境で運用するのが難しい。</li></ul><p>これらの制約を解消する方法は次回<a href="/blog/2018/05/30/practical-kubernetes-with-rails/">Kubernetes応用編</a>で取り上げたいと思います。</p><h1 id="参考情報"><a href="#参考情報" class="headerlink" title="参考情報"></a>参考情報</h1><ul><li><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/" target="_blank" rel="noopener">Kubernetes API Reference Docs(v1.10)</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。
この記事はシリーズ連載記事の第四回です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一回 &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/&quot;&gt;Docker編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第二回 &lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第三回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-tutorial/&quot;&gt;Kubernetes入門編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第四回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-with-rails/&quot;&gt;Kubernetes基礎編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第五回 &lt;a href=&quot;/blog/2018/05/30/practical-kubernetes-with-rails/&quot;&gt;Kubernetes応用編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第六回 &lt;a href=&quot;/blog/2018/05/30/helm-with-rails/&quot;&gt;Helm編&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回は以下のサンプルアプリケーションをminikubeにデプロイするためのマニフェストや手順について説明します。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kwhrtsk/rails-k8s-demoapp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kwhrtsk/rails-k8s-demoapp&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;前提&quot;&gt;&lt;a href=&quot;#前提&quot; class=&quot;headerlink&quot; title=&quot;前提&quot;&gt;&lt;/a&gt;前提&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;macOSでの作業を前提としています。&lt;/li&gt;
&lt;li&gt;使用したツールのバージョンなどは &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/#前提&quot;&gt;初回&lt;/a&gt; の記事を参照してください。&lt;/li&gt;
&lt;li&gt;ツールのインストール手順は &lt;a href=&quot;/blog/2018/05/30/kubernetes-tutorial/#開発環境の構築&quot;&gt;前回&lt;/a&gt; の記事を参照してください。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;準備&quot;&gt;&lt;a href=&quot;#準備&quot; class=&quot;headerlink&quot; title=&quot;準備&quot;&gt;&lt;/a&gt;準備&lt;/h1&gt;&lt;p&gt;まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。
(前回と同じなのですでにやっている人は読み飛ばしてください)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# サンプルアプリのコードをチェックアウト
$ 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
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;概要&quot;&gt;&lt;a href=&quot;#概要&quot; class=&quot;headerlink&quot; title=&quot;概要&quot;&gt;&lt;/a&gt;概要&lt;/h1&gt;&lt;p&gt;前回は下記について説明しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;APIオブジェクトを定義するマニフェストファイルをYAML形式で記述する方法&lt;/li&gt;
&lt;li&gt;マニフェストファイルと&lt;code&gt;kubectl&lt;/code&gt;コマンドを使ってAPIオブジェクトを管理する方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回は実際にRailsアプリをMySQLやRedisなどのミドルウェアも含めて丸ごとk8sクラスタ上にデプロイするための手順を説明します。
一度に多種のAPIオブジェクトを使うと全体を理解するのが難しくなるので、
4つのステップに分けて進めます。&lt;/p&gt;
&lt;p&gt;Step1では、第2回&lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/#docker-composeコマンドによるローカルプレビュー環境&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt;
の&lt;code&gt;docker-compose-preview.yml&lt;/code&gt;に相当する構成をできるだけ少ないAPIオブジェクトで簡潔に記述します。
この時点では簡潔さと引き換えにいくつかの制約がありますが、それらをStep2からStep4で解消していきます。&lt;/p&gt;
&lt;p&gt;今回の&lt;a href=&quot;/blog/2018/05/30/kubernetes-with-rails/&quot;&gt;Kubernetes基礎編&lt;/a&gt;ではStep1を説明し、
次回の&lt;a href=&quot;/blog/2018/05/30/practical-kubernetes-with-rails/&quot;&gt;Kubernetes応用編&lt;/a&gt;でStep2以降を説明します。&lt;/p&gt;
    
    </summary>
    
      <category term="HowTo" scheme="https://chopschips.net/categories/HowTo/"/>
    
    
      <category term="rails" scheme="https://chopschips.net/tags/rails/"/>
    
      <category term="docker" scheme="https://chopschips.net/tags/docker/"/>
    
      <category term="kubernetes" scheme="https://chopschips.net/tags/kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>Railsアプリ開発のためのDocker/Kubernetes入門3 Kubernetes入門編</title>
    <link href="https://chopschips.net/blog/2018/05/30/kubernetes-tutorial/"/>
    <id>https://chopschips.net/blog/2018/05/30/kubernetes-tutorial/</id>
    <published>2018-05-30T13:20:00.000Z</published>
    <updated>2018-05-30T15:56:29.835Z</updated>
    
    <content type="html"><![CDATA[<p>RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。この記事はシリーズ連載記事の第三回です。</p><ul><li>第一回 <a href="/blog/2018/05/30/docker-with-rails/">Docker編</a></li><li>第二回 <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a></li><li>第三回 <a href="/blog/2018/05/30/kubernetes-tutorial/">Kubernetes入門編</a></li><li>第四回 <a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes基礎編</a></li><li>第五回 <a href="/blog/2018/05/30/practical-kubernetes-with-rails/">Kubernetes応用編</a></li><li>第六回 <a href="/blog/2018/05/30/helm-with-rails/">Helm編</a></li></ul><p>今回は下記について書きます。</p><ul><li>最小限のk8s入門</li><li>minikubeの使い方</li><li><code>kubectl</code>コマンドのチュートリアル</li></ul><h1 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h1><ul><li>macOSでの作業を前提としています。</li><li>使用したツールのバージョンなどは <a href="/blog/2018/05/30/docker-with-rails/#前提">初回</a> の記事を参照してください。</li></ul><h1 id="はじめに"><a href="#はじめに" class="headerlink" title="はじめに"></a>はじめに</h1><p>Kubernetesには膨大な機能があるので、最初から汎用的な使い方を学ぼうとすると挫折しがちです。このドキュメントでは、紹介する機能や概念を「初心者がRailsアプリを動かすために必要な機能」という観点で限定し、かつ段階的に説明していきます。</p><p>まずは <a href="https://github.com/kubernetes/minikube" target="_blank" rel="noopener">minikube</a>を使ってmacOS上の仮想マシンにスタンドアローンのk8sクラスタを作り、そこに前回 <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a> で使用したサンプルアプリをデプロイする具体的な手順を示します。</p><p>紹介するサンプルコードは全て下記のリポジトリにあります。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp" target="_blank" rel="noopener">https://github.com/kwhrtsk/rails-k8s-demoapp</a></p><h1 id="開発環境の構築"><a href="#開発環境の構築" class="headerlink" title="開発環境の構築"></a>開発環境の構築</h1><p>下記のツールを使います。</p><ul><li><code>minikube</code>k8sクラスタをローカルVM上に構築するためのツールです。</li><li><code>hyperkitのドライバ</code>minikubeがmacOS上でVMを操作するために必要です。</li><li><code>kubectl</code>k8sクラスタを操作するためのCLIツールです。</li><li><code>helm</code>k8sのマニフェストをパッケージとして管理するためのツールです。第六回 <a href="/blog/2018/05/30/helm-with-rails/">Helm編</a>で使用します。</li><li><code>stern</code>k8s上のコンテナのログを見るためのツールです。必須ではありませんがあると便利です。</li><li><code>GNU Make</code>XCode付属のものを使います。無くても大丈夫です。</li></ul><p>minikubeとkubectlとsternはHomebrew(cask)でインストールできます。</p><pre><code>$ brew cask install minikube$ brew install kubernetes-cli kubernetes-helm$ brew install stern</code></pre><p>hyperkitのドライバは次の手順でインストールしてください。<a href="https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#hyperkit-driver" target="_blank" rel="noopener">参考</a></p><pre><code>$ curl -LO https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-hyperkit \&amp;&amp; chmod +x docker-machine-driver-hyperkit \&amp;&amp; sudo mv docker-machine-driver-hyperkit /usr/local/bin/ \&amp;&amp; sudo chown root:wheel /usr/local/bin/docker-machine-driver-hyperkit \&amp;&amp; sudo chmod u+s /usr/local/bin/docker-machine-driver-hyperkit</code></pre><p>macOS版のminikubeでは、hyperkitの代わりにVirtualBoxを使うこともできます。全ての手順は両方で動作を確認していますが、hyperkitの方が高速です。筆者の環境ではRailsアプリやMySQLなど一式k8sにデプロイするのにかかる時間はVirtualBoxだとおおよそ2分20秒前後、hyperkitだと50秒前後でした。</p><h1 id="最小限のKubernetes-k8s-入門"><a href="#最小限のKubernetes-k8s-入門" class="headerlink" title="最小限のKubernetes(k8s)入門"></a>最小限のKubernetes(k8s)入門</h1><h2 id="Kubernetesとは"><a href="#Kubernetesとは" class="headerlink" title="Kubernetesとは"></a>Kubernetesとは</h2><p>k8sはDocker Composeと同じく複数のコンテナを管理するためのツールです。</p><p>Docker Composeでは一つのホスト上に複数のコンテナを起動することができましたが、k8sでは複数のDockerサーバで一つのクラスタを構成し、その上に複数のコンテナを分散配置することができます。</p><p>また、Docker Composeは単にコンテナの起動や停止だけでなく、コンテナ間通信やボリュームの管理を行うことができましたが、k8sはさらにロードバランサや定期実行ジョブなど実環境で必要になる多様な機能を抽象化し管理することができます。</p><h2 id="minikubeとは"><a href="#minikubeとは" class="headerlink" title="minikubeとは"></a>minikubeとは</h2><p>k8sをセットアップして使えるようにするためには色々な方法があります。代表的なものをいくつか紹介します。</p><ul><li>GKE: GCP上のマネージドサービス</li><li>kops: AWS上にec2インスタンスを複数起動してk8sクラスタとして構成するツール</li><li>AKS: Azure上のマネージドサービス</li><li>kubespray: オンプレミスを含む任意の環境にk8sクラスタをセットアップするツール</li></ul><p>本番環境でk8sを運用するためには上記のようなものを使う必要がありますが、k8sを試してみるだけなら手元の環境で動作する<a href="https://github.com/kubernetes/minikube" target="_blank" rel="noopener">minikube</a>を使うのが一番簡単です。</p><p>minikubeは端末上のVMへk8sをインストールして簡単に使えるようにするためのツールです。VMドライバとしてはVirtualBox、hyperkit、KVMなどがサポートされています。</p><p><a href="https://github.com/kubernetes/minikube/blob/master/docs/drivers.md" target="_blank" rel="noopener">minikube/drivers.md</a></p><p>macOS上で試すのであれば VirtualBox か hyperkit が簡単です。デフォルトでは VirtualBox が選択されるようですが、hyperkitの方が高速なのでこちらを使うのがおすすめです。</p><p><a href="https://github.com/moby/hyperkit" target="_blank" rel="noopener">hyperkit</a>はmacOS組み込みの仮想化フレームワーク「Hypervisor Framework」を使うためのツールで、Docker for Macでも使われています。</p><p>次節以降ではminikubeの基本的な使い方を説明します。</p><a id="more"></a><h3 id="minikube-start-k8sクラスタ用VMの作成と起動"><a href="#minikube-start-k8sクラスタ用VMの作成と起動" class="headerlink" title="minikube start: k8sクラスタ用VMの作成と起動"></a><code>minikube start</code>: k8sクラスタ用VMの作成と起動</h3><p>下記のコマンドを実行すると、CPUコア3つ、メモリ2GBを割り当ててminikube専用のVMインスタンスを起動することができます。</p><pre><code># hyperkitを使う場合$ minikube start --cpus=3 --memory=2048 --vm-driver=hyperkit --disk-size=12g# virtualboxを使う場合$ minikube start --cpus=3 --memory=2048 --vm-driver=virtualbox</code></pre><p>このドキュメントで紹介する手順は全て上記の構成で動作を確認しています。</p><p>hyperkitを使う場合、<code>--disk-size</code>を指定しないと無条件に20GBのファイルが<code>~/.minikube/machines/minikube/minikube.rawdisk</code> に作成されます。ホスト側のストレージが足りない場合はこれが原因で起動に失敗することがあるので注意してください。上記の例では12GB割り当てています。</p><p>VirtualBoxを使う場合、ストレージファイルは必要に応じて拡張されていくのでサイズ指定は省略しています。ストレージの消費量は起動直後で2GB程度です。</p><h3 id="minikube-dashboard-k8sダッシュボード"><a href="#minikube-dashboard-k8sダッシュボード" class="headerlink" title="minikube dashboard: k8sダッシュボード"></a><code>minikube dashboard</code>: k8sダッシュボード</h3><p>下記のコマンドを実行すると、ブラウザでk8sのダッシュボードが開かれます。</p><pre><code>$ minikube dashboard</code></pre><p>このダッシュボード上では、k8sクラスタ上の各種オブジェクトの状態の確認や編集を行うことができます。</p><img src="/images/post_20180502_kubernetes_with_rails/minikube-dashboard.png"><h3 id="minikube-docker-env-VM上のdockerdに接続するための環境変数の表示"><a href="#minikube-docker-env-VM上のdockerdに接続するための環境変数の表示" class="headerlink" title="minikube docker-env: VM上のdockerdに接続するための環境変数の表示"></a><code>minikube docker-env</code>: VM上のdockerdに接続するための環境変数の表示</h3><p>minikubeが動作しているVM上でもDockerプロセスが動作しています。</p><p>普段 <code>docker image ls</code> などのコマンドを実行するとDocker for MacのDockerプロセスに接続していますが、下記のように<code>minikube docker-env</code>で出力される環境変数をロードするとminikube VM上のDockerプロセスに接続することができます。</p><pre><code>$ eval $(minikube docker-env)$ docker image lsREPOSITORY                                   TAG        IMAGE ID        CREATED        SIZEk8s.gcr.io/kube-proxy-amd64                  v1.10.0    bfc21aadc7d3    5 weeks ago    97MBk8s.gcr.io/kube-apiserver-amd64              v1.10.0    af20925d51a3    5 weeks ago    225MBk8s.gcr.io/kube-controller-manager-amd64     v1.10.0    ad86dbed1555    5 weeks ago    148MBk8s.gcr.io/kube-scheduler-amd64              v1.10.0    704ba848e69a    5 weeks ago    50.4MB(省略)</code></pre><p>通常、自分でビルドしたイメージをk8s上でコンテナとして起動するためには、何らかのレジストリサービスにそのイメージを登録する必要があるのですが、このドキュメントでは手順を簡単にするためにminikube VM上のDockerプロセスに直接接続してRailsアプリのイメージをビルドする方法を紹介します。これについての詳細は次回<a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes応用編</a>で説明します。</p><h3 id="minikube-delete-k8sクラスタ用VMの停止と削除"><a href="#minikube-delete-k8sクラスタ用VMの停止と削除" class="headerlink" title="minikube delete: k8sクラスタ用VMの停止と削除"></a><code>minikube delete</code>: k8sクラスタ用VMの停止と削除</h3><p>下記のコマンドを実行すると、k8s用に作成したVMを丸ごと削除します。気軽にk8sクラスタの初期化を行えるのがminikubeの良いところです。</p><pre><code>$ minikube delete</code></pre><h2 id="k8sの基本"><a href="#k8sの基本" class="headerlink" title="k8sの基本"></a>k8sの基本</h2><p>k8sではコンテナを直接操作することはありません。<code>Deployment</code>や<code>Service</code>など抽象化されたリソースを介してコンテナの起動や停止を行います。これらのリソースをk8sでは<code>APIオブジェクト</code>と呼びます(単にオブジェクトと呼ばれることもあります)。</p><p>k8sクラスタにAPIオブジェクトを作成したり更新したりする方法には大まかに3つの方法があります。</p><ol><li>YAML形式のマニフェストファイルを<code>kubectl apply -f</code>で指定して実行する。</li><li><code>kubectl create deployment</code>や<code>kubectl run</code>、<code>kubectl expose</code>などのコマンドを実行する。</li><li>ダッシュボード上のメニューで作成や更新を実行する。</li></ol><p>インフラ構成を管理する上で一番望ましいのは、構成をコード化してバージョン管理できる1の方法です。このドキュメントでは、一部の例外をのぞいて全て1の方法で説明します。</p><p>一部の例外というのは以下の二つです。詳細は後述します。</p><ul><li>TLS証明書用の<code>Secret</code>オブジェクトの作成</li><li>テストを目的とした対話的な操作のための一時的なコンテナの作成</li></ul><h3 id="準備"><a href="#準備" class="headerlink" title="準備"></a>準備</h3><p>まずサンプルアプリのコードをチェックアウトしてminikubeを起動してください。</p><pre><code># サンプルアプリのコードをチェックアウト$ 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</code></pre><h2 id="APIオブジェクトのマニフェストファイルとkubectlのチュートリアル"><a href="#APIオブジェクトのマニフェストファイルとkubectlのチュートリアル" class="headerlink" title="APIオブジェクトのマニフェストファイルとkubectlのチュートリアル"></a>APIオブジェクトのマニフェストファイルとkubectlのチュートリアル</h2><p>この節ではAPIオブジェクトの例として<code>Deployment</code>と<code>Service</code>を取り上げ、<code>kubectl</code>コマンドを使用してオブジェクトの作成などを行う方法を説明します。</p><h3 id="Deploymentの概要"><a href="#Deploymentの概要" class="headerlink" title="Deploymentの概要"></a><code>Deployment</code>の概要</h3><p>k8sの最も重要なAPIオブジェクトの一つは<code>Deployment</code>です。状態を持たないアプリケーションのコンテナは<code>Deployment</code>オブジェクトによって管理します。</p><p><code>Deployment</code>オブジェクトを作成すると、その配下へ自動的に<code>ReplicaSet</code>と<code>Pod</code>というオブジェクトが作成されます。</p><p><code>Pod</code>はk8sにおいてコンテナを管理するための最小単位です。多くの場合、コンテナと一対一に対応しています。(一つの<code>Pod</code>に複数のコンテナを入れることもできますが、それは<a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/#how-pods-manage-multiple-containers" target="_blank" rel="noopener">高度なユースケースである</a>とドキュメントにも書いてありますし、ここでは取り上げません)</p><p><code>ReplicaSet</code>は、同じ設定で作られた<code>Pod</code>が<code>replicas</code>パラメータで指定された個数だけ維持されるように調整します。</p><pre><code>        +--------------+        |  Deployment  |        +------+-------+               |        +------+-------+        |  ReplicaSet  |   replicas: 2        +-+----------+-+          |          |      +---+---+  +---+---+      |  Pod  |  |  Pod  |      +-------+  +-------+</code></pre><p><code>Deployment</code>は、<code>ReplicaSet</code>を管理します。コンテナイメージの更新などを行う際には新しい設定の<code>ReplicaSet</code>を作成して、古い<code>ReplicaSet</code>から<code>Pod</code>を削除しながら新しい<code>ReplicaSet</code>に<code>Pod</code>を作成します。</p><pre><code>              +--------------+              |  Deployment  |              +------------+-+                :          |   +------------+-+      +-+----------+-+     |  ReplicaSet  |  -&gt;  |  ReplicaSet  |     +-+----------+-+      +-+----------+-+       |          |          |          |     +---+---+  +---+---+  +---+---+  +---+---+ |  Pod  |  |  Pod  |  |  Pod  |  |  Pod  | +-------+  +-------+  +-------+  +-------+</code></pre><h3 id="Deploymentのマニフェストファイルの例"><a href="#Deploymentのマニフェストファイルの例" class="headerlink" title="Deploymentのマニフェストファイルの例"></a><code>Deployment</code>のマニフェストファイルの例</h3><p>以下のYAMLデータは<code>mysql:5.7.21</code>のコンテナを一つ起動するだけの<code>Deployment</code>のマニフェストファイルです。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step0/mysql-deploy.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1   <span class="token comment" spellcheck="true"># このYAMLデータのスキーマバージョン</span><span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment      <span class="token comment" spellcheck="true"># オブジェクトの種類</span><span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> mysql         <span class="token comment" spellcheck="true"># オブジェクトの名前</span>  <span class="token key atrule">labels</span><span class="token punctuation">:</span>             <span class="token comment" spellcheck="true"># オブジェクトを選択するために使うラベル(後述)</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> mysql        <span class="token comment" spellcheck="true"># 任意のキー・値を設定して良い</span><span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">1         </span><span class="token comment" spellcheck="true"># 起動するPodの数</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>               <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>      <span class="token comment" spellcheck="true"># .spec.template.metadata.labelsと一致させる必要がある</span>      <span class="token key atrule">app</span><span class="token punctuation">:</span> mysql  <span class="token key atrule">template</span><span class="token punctuation">:</span>    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>      <span class="token key atrule">labels</span><span class="token punctuation">:</span>         <span class="token comment" spellcheck="true"># ReplicaSetとPodに付けられるラベル</span>        <span class="token key atrule">app</span><span class="token punctuation">:</span> mysql    <span class="token key atrule">spec</span><span class="token punctuation">:</span>      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> Always   <span class="token comment" spellcheck="true"># コンテナが死亡した際の対応</span>      <span class="token key atrule">containers</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> mysql         <span class="token comment" spellcheck="true"># コンテナのNAME属性に使用</span>          <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.21 <span class="token comment" spellcheck="true"># コンテナイメージ</span>          <span class="token key atrule">env</span><span class="token punctuation">:</span>                <span class="token comment" spellcheck="true"># 環境変数を指定</span>            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> MYSQL_ALLOW_EMPTY_PASSWORD              <span class="token key atrule">value</span><span class="token punctuation">:</span> <span class="token string">"yes"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" target="_blank" rel="noopener">Deploymentのドキュメント</a></li><li><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#deployment-v1-apps" target="_blank" rel="noopener">DeploymentのAPIリファレンス</a></li></ul><p>簡単な注釈を加えていますが、わかりにくい部分をいくつか補足します。</p><hr><ul><li><code>.apiVersion</code></li></ul><p><code>apps/v1</code>の他に、<code>v1</code>、<code>extensions/v1beta1</code>などの値を取る場合があり、<code>kind</code>と<code>apiVersion</code>の組み合わせによってYAMLデータの構造が決まります。</p><p>各オブジェクトの<code>apiVersion</code>の推奨値はk8sのAPIバージョンによって決まっていて、例えば2018年5月30日時点で最新のk8sのバージョン1.10では、<code>Deployment</code>の<code>apiVersion</code>の推奨値は<code>apps/v1</code>になります。</p><p>参考: <a href="https://kubernetes.io/docs/reference/workloads-18-19/" target="_blank" rel="noopener">Workloads API changes in versions 1.8 and 1.9 | Kubernetes</a></p><ul><li><code>.metadata.labels</code></li></ul><p>ラベルです。他のAPIオブジェクトの設定からこの<code>Deployment</code>を参照する際に使用する場合があります。また、<code>kubectl get</code>など一部のCLIツールでオブジェクトをフィルタリングする際にも使用できます。</p><ul><li><code>.spec.selector.matchLabels</code></li></ul><p>以前のバージョンでは省略できたのですが、<code>apiVersion</code>が<code>apps/v1</code>になって必須パラメータになりました。必ず <code>.spec.template.metadata.labels</code> にマッチしている必要があります。書き方によっては、別の<code>Deployment</code>で定義したオブジェクトを参照してしまう場合があるので注意が必要です。</p><ul><li><code>.spec.template.spec.restartPolicy</code></li></ul><p>コンテナが停止した時に再起動するかどうかをAlways, OnFailure, Neverのいずれかで指定します。<a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy" target="_blank" rel="noopener">参考</a></p><hr><p>このファイルはサンプルコードの<a href="https://github.com/kwhrtsk/rails-k8s-demoapp/blob/master/k8s/manifests-step0/mysql-deploy.yaml" target="_blank" rel="noopener">k8s/manifests-step0/mysql-deploy.yaml</a>に置いてあります。</p><p>以降、このマニフェストファイルを使って<code>kubectl</code>コマンドの基本的な使い方を説明します。</p><h3 id="kubectl-apply-APIオブジェクトの作成と更新"><a href="#kubectl-apply-APIオブジェクトの作成と更新" class="headerlink" title="kubectl apply: APIオブジェクトの作成と更新"></a><code>kubectl apply</code>: APIオブジェクトの作成と更新</h3><p>前述のYAMLファイルを使ってk8sクラスタに<code>Deployment</code>オブジェクトを作るには下記のようにします。</p><pre><code>$ kubectl apply -f k8s/manifests-step0/mysql-deploy.yamldeployment.apps &quot;mysql&quot; created</code></pre><p>複数のYAMLファイルを指定する場合は下記のように標準入力で指定します。</p><pre><code>$ cat k8s/manifests-step0/*.yaml | kubectl apply -f -</code></pre><p>この際、各オブジェクトの定義が <code>---</code> で区切られている必要がある点に注意してください。上記のように<code>cat</code>で結合しやすいように、あらかじめ各YAMLファイルの先頭に <code>---</code> を入れておくのが良いと思います。</p><p>作成したオブジェクトの設定を更新したい場合は、マニフェストファイルを編集してもう一度同じコマンドを実行します。</p><p>以前のバージョンでは、作成は<code>kubectl create -f</code>、更新は<code>kubectl apply -f</code>と使い分けていましたが、現在は両方とも<code>kubectl apply -f</code>を使います。</p><ul><li><a href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#apply" target="_blank" rel="noopener">kubectl apply | Kubectl Reference Docs</a></li></ul><h3 id="kubectl-get-APIオブジェクトの状態の確認"><a href="#kubectl-get-APIオブジェクトの状態の確認" class="headerlink" title="kubectl get: APIオブジェクトの状態の確認"></a><code>kubectl get</code>: APIオブジェクトの状態の確認</h3><p><code>Deployment</code>、<code>ReplicaSet</code>、<code>Pod</code>オブジェクトの状態を確認するには、下記のようにします。</p><pre><code>$ kubectl get deployments,replicaset,podsNAME                          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGEdeployment.extensions/mysql   1         1         1            1           1sNAME                                     DESIRED   CURRENT   READY     AGEreplicaset.extensions/mysql-55fb9dc7db   1         1         1         1sNAME                         READY     STATUS    RESTARTS   AGEpod/mysql-55fb9dc7db-5mh8v   1/1       Running   0          1s</code></pre><p><code>kubectl get</code>のパラメータにはAPIオブジェクトの種類を示す文字列を指定しますが、略称がある場合はそれを使えます。</p><pre><code># kubectl get deploymentsと同じ$ kubectl get deploy</code></pre><p>このドキュメントで扱う主要なAPIオブジェクトの略称は下記の通りです。</p><table><thead><tr><th>名称</th><th>略称</th></tr></thead><tbody><tr><td>deployments</td><td>deploy</td></tr><tr><td>configmaps</td><td>cm</td></tr><tr><td>deployments</td><td>deploy</td></tr><tr><td>ingresses</td><td>ing</td></tr><tr><td>jobs</td><td>無し</td></tr><tr><td>namespaces</td><td>ns</td></tr><tr><td>persistentvolumeclaims</td><td>pvc</td></tr><tr><td>persistentvolumes</td><td>pv</td></tr><tr><td>pods</td><td>po</td></tr><tr><td>replicasets</td><td>rs</td></tr><tr><td>secrets</td><td>無し</td></tr><tr><td>services</td><td>svc</td></tr><tr><td>storageclasses</td><td>sc</td></tr></tbody></table><p>完全なリストはパラメータ無しで <code>kubectl get</code> を実行すると表示されます。</p><ul><li><a href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#get" target="_blank" rel="noopener">kubectl get | Kubectl Reference Docs</a></li></ul><h3 id="kubectl-logs-またはstern-コンテナのログの確認"><a href="#kubectl-logs-またはstern-コンテナのログの確認" class="headerlink" title="kubectl logs (またはstern): コンテナのログの確認"></a><code>kubectl logs</code> (またはstern): コンテナのログの確認</h3><p>コンテナのログを確認するには、オブジェクトの種類とNAMEを指定して下記のようにします。NAMEは前述の<code>kubectl get</code>で確認できます。</p><pre><code># Deploymentを指定する場合$ kubectl logs deployments/mysql# ReplicaSetを指定する場合$ kubectl logs replicasets/mysql-55fb9dc7db# Podを指定する場合$ kubectl logs pods/mysql-55fb9dc7db-5mh8v</code></pre><p>これは<code>docker container logs</code>相当の機能です。コンテナの標準出力と標準エラー出力の履歴を確認できます。</p><p><code>Pod</code>を指定する場合、マニフェストファイルで設定したラベル(<code>.spec.template.metadata.labels</code>)の値を使って、下記のように指定できます。</p><pre><code>$ kubectl logs --selector &quot;app=mysql&quot;# ラベルが複数ある場合はカンマ区切りでAND指定$ kubectl logs --selector &quot;app=demoapp,component=mysql&quot;</code></pre><p>ログの閲覧は <a href="https://github.com/wercker/stern" target="_blank" rel="noopener">stern</a> を使うともっと簡単です。<code>Pod</code>の名前に対して正規表現でマッチさせることができ、複数の<code>Pod</code>のログを一度にtailすることができます。</p><pre><code>$ stern &quot;mysql.*&quot;</code></pre><ul><li><a href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs" target="_blank" rel="noopener">kubectl logs | Kubectl Reference Docs</a></li></ul><h3 id="kubectl-port-forward-コンテナのポートをホスト側にポートフォワード"><a href="#kubectl-port-forward-コンテナのポートをホスト側にポートフォワード" class="headerlink" title="kubectl port-forward: コンテナのポートをホスト側にポートフォワード"></a><code>kubectl port-forward</code>: コンテナのポートをホスト側にポートフォワード</h3><p>Docker Composeでは<code>ports</code>エントリをYAMLファイルに書いておくだけでコンテナのポートをホスト側にマッピングすることができましたが、k8sではクラスタの外側からコンテナに接続するためには<code>Service</code>や<code>Ingress</code>など別のAPIオブジェクトを設定する必要があります。</p><p>ただし、<code>kubectl port-forward</code>コマンドを使うと、k8sクラスタのノードにsshで接続して特定のコンテナのポートをホストOSにポートフォワードすることができます。このコマンドでは<code>Deployment</code>または<code>Pod</code>の名前をパラメータに指定する必要があります。</p><pre><code># Deploymentを指定する場合$ kubectl port-forward deployment/mysql 3306:3306# TYPEを省略するとPodを指定したことになる$ kubectl port-forward mysql-55fb9dc7db-5mh8v 3306:3306</code></pre><p>ポート番号は左側がホスト側のポートです。コンテナとホストのポートが同じ場合は<code>3306</code>のように番号を一つだけ指定することもできます。</p><p>別のターミナルで下記のコマンドを実行すると、k8s上のmysqlコンテナに接続することができます。オブジェクトの起動直後はエラーになる場合があるので、数秒待ってから試してみてください。</p><pre><code>$ mysql -u root --host 0.0.0.0 --port 3306</code></pre><p><code>Pod</code>の名前を指定する場合は、下記のようにするとラベルでオブジェクトを選択することができます。</p><pre><code>$ kubectl port-forward $(kubectl get pods --selector &quot;app=mysql&quot; --output &quot;jsonpath={.items..metadata.name}&quot;) 3306</code></pre><ul><li><a href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#port-forward" target="_blank" rel="noopener">kubectl port-forward | Kubectl Reference Docs</a></li></ul><h3 id="kubectl-delete-APIオブジェクトの削除"><a href="#kubectl-delete-APIオブジェクトの削除" class="headerlink" title="kubectl delete: APIオブジェクトの削除"></a>kubectl delete: APIオブジェクトの削除</h3><p><code>kubectl apply -f</code>で指定したファイルを指定してオブジェクトを削除することができます。</p><pre><code>$ kubectl delete -f k8s/manifests-step0/mysql-deploy.yaml</code></pre><ul><li><a href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#delete" target="_blank" rel="noopener">kubectl delete | Kubectl Reference Docs</a></li></ul><h3 id="Serviceの概要"><a href="#Serviceの概要" class="headerlink" title="Serviceの概要"></a><code>Service</code>の概要</h3><p>k8sの最も重要なAPIオブジェクトのもう一つは<code>Service</code>です。</p><p>前節では<code>mysql</code>コンテナを管理する<code>Deployment</code>の例を示しましたが、別の<code>Pod</code>からこのMySQLサーバにアクセスするためには<code>Pod</code>のIPアドレスとポートを指定する必要があります。<code>Pod</code>のIPアドレスは起動するたびに変わってしまうので、アプリケーションの設定に記述することはできません。</p><p><code>Service</code>オブジェクトを作ると、ラベルで指定した<code>Pod</code>群へアクセスできるIPアドレスとDNS名がk8sによって用意されます。</p><p>前節の<code>mysql</code>の<code>Deployment</code>に対応する<code>Service</code>定義の例を示します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># k8s/manifests-step0/mysql-svc.yaml</span><span class="token punctuation">---</span><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1<span class="token key atrule">kind</span><span class="token punctuation">:</span> Service<span class="token key atrule">metadata</span><span class="token punctuation">:</span>  <span class="token key atrule">name</span><span class="token punctuation">:</span> mysql  <span class="token key atrule">labels</span><span class="token punctuation">:</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> mysql<span class="token key atrule">spec</span><span class="token punctuation">:</span>  <span class="token key atrule">type</span><span class="token punctuation">:</span> ClusterIP  <span class="token key atrule">ports</span><span class="token punctuation">:</span>    <span class="token punctuation">-</span> <span class="token key atrule">protocol</span><span class="token punctuation">:</span> TCP      <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3306     </span><span class="token comment" spellcheck="true"># 接続するPod側のポートを指定</span>  <span class="token key atrule">selector</span><span class="token punctuation">:</span>          <span class="token comment" spellcheck="true"># 接続するPodのラベルを指定</span>    <span class="token key atrule">app</span><span class="token punctuation">:</span> mysql<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上記のマニフェストを<code>kubectl apply</code>で実行して<code>Service</code>オブジェクトを作ると、k8sクラスタ上のコンテナ上では<code>Service</code>オブジェクトの名前である<code>&quot;mysql&quot;</code>という文字列をホスト名として指定することで<code>mysql</code>の<code>Deployment</code>が管理している<code>Pod</code>にアクセスできるようになります。</p><p><code>.spec.type</code>には<code>ClusterIP</code>、<code>NodePort</code>、<code>LoadBalancer</code>、<code>ExternalName</code>のいずれかを指定します。<code>ClusterIP</code>は、この<code>Service</code>オブジェクトが作るエンドポイントがk8sクラスタの内部向けであることを意味します。k8sクラスタの外側からアクセスできるようなエンドポイントを作る場合には<code>NodePort</code>または<code>LoadBalancer</code>を指定します。このドキュメントでは、最終的には<code>Ingress</code>オブジェクトを使用して外部向けのエンドポイントを用意するため、<code>NodePort</code>や<code>LoadBalancer</code>の詳細については割愛します。また、<code>ExternalName</code>を使うとk8sの外側のサービスのエイリアスを作ることができますが、このドキュメントでは扱いません。</p><ul><li><a href="https://kubernetes.io/docs/concepts/services-networking/service/" target="_blank" rel="noopener">Serviceのドキュメント</a></li><li><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#service-v1-core" target="_blank" rel="noopener">ServiceのAPIリファレンス</a></li></ul><h3 id="kubectl-run-コマンドによるDeploymentの作成"><a href="#kubectl-run-コマンドによるDeploymentの作成" class="headerlink" title="kubectl run: コマンドによるDeploymentの作成"></a><code>kubectl run</code>: コマンドによる<code>Deployment</code>の作成</h3><p>インフラ構成管理の観点からは全ての<code>Deployment</code>はマニフェストファイルで管理するのが望ましいのですが、k8sクラスタ上に一時的なコンテナを作成して簡単なオペレーションを実行したい時もあります。</p><p>そのような場合には<code>kubectl run</code>コマンドを使うとYAMLファイル無しで<code>Deployment</code>を作成することができ便利です。フォアグラウンドで対話的なコマンドを実行することもできるため、トラブルシュートにも最適です。</p><p>利用例として、前節までに紹介した<code>mysql</code>用の<code>Deployment</code>と<code>Service</code>を作成した後、<code>kubectl run</code>コマンドでシェルを起動し、<code>Service</code>によって用意されたエンドポイント経由でMySQLサーバに接続する方法を示します。</p><p>まず<code>mysql</code>の<code>Deployment</code>と<code>Service</code>を作成します。</p><pre><code>$ kubectl apply -f k8s/manifests-step0/mysql-deploy.yamldeployment.apps &quot;mysql&quot; created$ kubectl apply -f k8s/manifests-step0/mysql-svc.yamlservice &quot;mysql&quot; created$ kubectl get deployments,replicasets,pods,servicesNAME                          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGEdeployment.extensions/mysql   1         1         1            1           9sNAME                                    DESIRED   CURRENT   READY     AGEreplicaset.extensions/mysql-b59b886c9   1         1         1         9sNAME                        READY     STATUS    RESTARTS   AGEpod/mysql-b59b886c9-955tg   1/1       Running   0          9sNAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGEservice/kubernetes   ClusterIP   10.96.0.1        &lt;none&gt;        443/TCP    1dservice/mysql        ClusterIP   10.110.107.218   &lt;none&gt;        3306/TCP   4s</code></pre><p><code>service/kubernetes</code>はシステム標準の<code>Service</code>です。<code>service/mysql</code>という<code>Service</code>オブジェクトが作成されているのを確認できます。</p><p>次に下記のコマンドで新たに<code>mysql:5.7.21</code>イメージの<code>Deployment</code>を作成します。コンテナの中では<code>mysqld</code>コマンドの代わりに対話モードで<code>bash</code>を起動して、プロンプトからMySQLサーバに接続します。</p><pre><code>$ kubectl run mysql-client --image=mysql:5.7.21 -it --rm --restart=Never /bin/bashIf you don&#39;t see a command prompt, try pressing enter.root@mysql-client:/#</code></pre><p>上記のようにシェルのプロンプトが表示されたら<code>mysql</code>コマンドを入力してください。</p><pre><code>root@mysql-client:/# mysql -u root --host mysqlWelcome to the MySQL monitor.  Commands end with ; or \g.Your MySQL connection id is 2Server version: 5.7.21 MySQL Community Server (GPL)Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type &#39;help;&#39; or &#39;\h&#39; for help. Type &#39;\c&#39; to clear the current input statement.mysql&gt;</code></pre><p>MySQLサーバに接続できました。<code>kubectl run</code>コマンドのパラメータの意味は次の通りです。</p><ul><li><code>mysql-client</code>は<code>Deployment</code>の名前です。</li><li><code>--image</code>はコンテナのイメージ名です。</li><li><code>-it</code>オプションは<code>docker container run(旧 docker run)</code>コマンドと同じ意味です。</li><li><code>--rm</code>オプションを指定すると、コマンドの終了時に自動的に<code>Deployment</code>削除されます。</li><li><code>--restart=Never</code>をつけておかないと<code>Deployment</code>が削除されるまでのわずかな間に<code>Pod</code>が再起動されるケースがあり、動作がやや不安定になります。</li></ul><h3 id="kubectl-explain-ドキュメントの表示"><a href="#kubectl-explain-ドキュメントの表示" class="headerlink" title="kubectl explain: ドキュメントの表示"></a><code>kubectl explain</code>: ドキュメントの表示</h3><p>すでに見てきたように、マニフェストファイルは結構複雑な構造のYAMLデータです。慣れないうちはリファレンスを見ながら書くことになると思いますが、公式サイトのドキュメントは個々の機能の解説に注力しているため、今ひとつマニフェスト定義自体の全貌をつかむことができません。</p><ul><li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" target="_blank" rel="noopener">Deploymentのドキュメント</a></li><li><a href="https://kubernetes.io/docs/concepts/services-networking/service/" target="_blank" rel="noopener">Serviceのドキュメント</a></li></ul><p>マニフェストの構造を確認するためにはAPIのリファレンスを参照する方が確実です。</p><ul><li><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#deployment-v1-apps" target="_blank" rel="noopener">DeploymentのAPIリファレンス</a></li><li><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#service-v1-core" target="_blank" rel="noopener">ServiceのAPIリファレンス</a></li></ul><p>また、<code>kubectl</code>には指定したオブジェクトのAPIリファレンスを参照する機能があります。Webよりもこちらの方が便利かもしれません。</p><pre><code>$ kubectl explain --api-version=apps/v1 deploymentKIND:     DeploymentVERSION:  apps/v1DESCRIPTION:     Deployment enables declarative updates for Pods and ReplicaSets.FIELDS:   apiVersion   &lt;string&gt;     APIVersion defines the versioned schema of this representation of an     object. Servers should convert recognized schemas to the latest internal     value, and may reject unrecognized values. More info:     https://git.k8s.io/community/contributors/devel/api-conventions.md#resources   kind &lt;string&gt;     Kind is a string value representing the REST resource this object     represents. Servers may infer this from the endpoint the client submits     requests to. Cannot be updated. In CamelCase. More info:     https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds   metadata     &lt;Object&gt;     Standard object metadata.   spec &lt;Object&gt;     Specification of the desired behavior of the Deployment.   status       &lt;Object&gt;     Most recently observed status of the Deployment.</code></pre><p>オブジェクトの名前に続けて<code>.</code>区切りでフィールドを指定するとネストした要素のドキュメントを参照できます。また、オブジェクトの名前は<code>kubectl get</code>と同じ略称を使うことができます。</p><pre><code>$ kubectl explain --api-version=apps/v1 deployment.spec.template.spec.containers.resourcesKIND:     DeploymentVERSION:  apps/v1RESOURCE: resources &lt;Object&gt;DESCRIPTION:     Compute Resources required by this container. Cannot be updated. More info:     https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources     ResourceRequirements describes the compute resource requirements.FIELDS:   limits       &lt;map[string]string&gt;     Limits describes the maximum amount of compute resources allowed. More     info:     https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/   requests     &lt;map[string]string&gt;     Requests describes the minimum amount of compute resources required. If     Requests is omitted for a container, it defaults to Limits if that is     explicitly specified, otherwise to an implementation-defined value. More     info:     https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/</code></pre><h1 id="参考情報"><a href="#参考情報" class="headerlink" title="参考情報"></a>参考情報</h1><p>Kubernetesのより詳細な情報は下記のドキュメントを参照してください。</p><ul><li><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/" target="_blank" rel="noopener">APIリファレンス(v1.10)</a><ul><li>マニフェストのスキーマに関する網羅的な定義はここが詳しいです。</li></ul></li><li><a href="https://kubernetes.io/docs/concepts/" target="_blank" rel="noopener">Concepts | Kubernetes</a><ul><li>メニューが階層化しているため慣れるまでわかりにくいですが、各種APIオブジェクトの解説はここが公式なドキュメントです。今回のドキュメントで取り上げたオブジェクトのドキュメントを挙げておきます。</li><li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" target="_blank" rel="noopener">Deployments</a></li><li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/" target="_blank" rel="noopener">ReplicaSet</a></li><li><a href="https://kubernetes.io/docs/concepts/workloads/pods/pod/" target="_blank" rel="noopener">Pods</a></li><li><a href="https://kubernetes.io/docs/concepts/services-networking/service/" target="_blank" rel="noopener">Services</a></li></ul></li><li><a href="https://kubernetes.io/docs/reference/kubectl/cheatsheet/" target="_blank" rel="noopener">kubectl Cheat Sheet | Kubernetes</a><ul><li><code>kubectl</code>コマンドのチートシートです。1度目を通しておくと引き出しが増えるのでおすすめです。</li></ul></li></ul><p>おすすめの書籍はこの2冊です。</p><div style="display: flex;"><iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="https://rcm-fe.amazon-adsystem.com/e/cm?ref=tf_til&t=chopschips03-22&m=amazon&o=9&p=8&l=as1&IS1=1&detail=1&asins=4798153222&linkId=db46cec2f20263827bcad4a1cd4c7b7f&bc1=000000&lt1=_blank&fc1=333333&lc1=0066c0&bg1=ffffff&f=ifr"></iframe><iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="https://rcm-fe.amazon-adsystem.com/e/cm?ref=tf_til&t=chopschips03-22&m=amazon&o=9&p=8&l=as1&IS1=1&detail=1&asins=4873118409&linkId=18062ba97ff4f47881d230671bb90713&bc1=000000&lt1=_blank&fc1=333333&lc1=0066c0&bg1=ffffff&f=ifr"></iframe></div><p>一冊めの「プログラマのためのDocker教科書」は前回の記事でも紹介した書籍です。おおよそ2章分と比較的大きく紙面を割いてKubernetesの概要と基礎的なAPIオブジェクトの説明がわかりやすく書いてあります。また、GCP上で開発を行う方法がコードの管理からイメージのビルドまで丁寧に説明してあり、付録でGCPの使い方まで説明されていて手厚いです。</p><p>一方、<code>Pod</code>の死活監視の設定や<code>Service</code>によるサービスディスカバリの詳細など、少し踏み込んだ内容は大胆に省略してあります。もう少し詳しい仕組みを知りたい場合は二冊目の「入門Kubernetes」で補うのが良いと思います。これ一冊で入門するのは正直しんどいと思うのですが、ある程度知識を持った状態で読むのであればおすすめです。</p><p>次回は <a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes基礎編</a> です。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。
この記事はシリーズ連載記事の第三回です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一回 &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/&quot;&gt;Docker編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第二回 &lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第三回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-tutorial/&quot;&gt;Kubernetes入門編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第四回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-with-rails/&quot;&gt;Kubernetes基礎編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第五回 &lt;a href=&quot;/blog/2018/05/30/practical-kubernetes-with-rails/&quot;&gt;Kubernetes応用編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第六回 &lt;a href=&quot;/blog/2018/05/30/helm-with-rails/&quot;&gt;Helm編&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回は下記について書きます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最小限のk8s入門&lt;/li&gt;
&lt;li&gt;minikubeの使い方&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt;コマンドのチュートリアル&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;前提&quot;&gt;&lt;a href=&quot;#前提&quot; class=&quot;headerlink&quot; title=&quot;前提&quot;&gt;&lt;/a&gt;前提&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;macOSでの作業を前提としています。&lt;/li&gt;
&lt;li&gt;使用したツールのバージョンなどは &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/#前提&quot;&gt;初回&lt;/a&gt; の記事を参照してください。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;はじめに&quot;&gt;&lt;a href=&quot;#はじめに&quot; class=&quot;headerlink&quot; title=&quot;はじめに&quot;&gt;&lt;/a&gt;はじめに&lt;/h1&gt;&lt;p&gt;Kubernetesには膨大な機能があるので、最初から汎用的な使い方を学ぼうとすると挫折しがちです。
このドキュメントでは、紹介する機能や概念を「初心者がRailsアプリを動かすために必要な機能」という観点で限定し、
かつ段階的に説明していきます。&lt;/p&gt;
&lt;p&gt;まずは &lt;a href=&quot;https://github.com/kubernetes/minikube&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;minikube&lt;/a&gt;を使ってmacOS上の仮想マシンにスタンドアローンのk8sクラスタを作り、
そこに前回 &lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt; で使用したサンプルアプリを
デプロイする具体的な手順を示します。&lt;/p&gt;
&lt;p&gt;紹介するサンプルコードは全て下記のリポジトリにあります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kwhrtsk/rails-k8s-demoapp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kwhrtsk/rails-k8s-demoapp&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;開発環境の構築&quot;&gt;&lt;a href=&quot;#開発環境の構築&quot; class=&quot;headerlink&quot; title=&quot;開発環境の構築&quot;&gt;&lt;/a&gt;開発環境の構築&lt;/h1&gt;&lt;p&gt;下記のツールを使います。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;minikube&lt;/code&gt;
k8sクラスタをローカルVM上に構築するためのツールです。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hyperkitのドライバ&lt;/code&gt;
minikubeがmacOS上でVMを操作するために必要です。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt;
k8sクラスタを操作するためのCLIツールです。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm&lt;/code&gt;
k8sのマニフェストをパッケージとして管理するためのツールです。第六回 &lt;a href=&quot;/blog/2018/05/30/helm-with-rails/&quot;&gt;Helm編&lt;/a&gt;で使用します。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stern&lt;/code&gt;
k8s上のコンテナのログを見るためのツールです。必須ではありませんがあると便利です。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GNU Make&lt;/code&gt;
XCode付属のものを使います。無くても大丈夫です。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;minikubeとkubectlとsternはHomebrew(cask)でインストールできます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ brew cask install minikube
$ brew install kubernetes-cli kubernetes-helm
$ brew install stern
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;hyperkitのドライバは次の手順でインストールしてください。&lt;a href=&quot;https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#hyperkit-driver&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;参考&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ curl -LO https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-hyperkit \
&amp;amp;&amp;amp; chmod +x docker-machine-driver-hyperkit \
&amp;amp;&amp;amp; sudo mv docker-machine-driver-hyperkit /usr/local/bin/ \
&amp;amp;&amp;amp; sudo chown root:wheel /usr/local/bin/docker-machine-driver-hyperkit \
&amp;amp;&amp;amp; sudo chmod u+s /usr/local/bin/docker-machine-driver-hyperkit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;macOS版のminikubeでは、hyperkitの代わりにVirtualBoxを使うこともできます。
全ての手順は両方で動作を確認していますが、hyperkitの方が高速です。
筆者の環境ではRailsアプリやMySQLなど一式k8sにデプロイするのにかかる時間はVirtualBoxだとおおよそ2分20秒前後、
hyperkitだと50秒前後でした。&lt;/p&gt;
&lt;h1 id=&quot;最小限のKubernetes-k8s-入門&quot;&gt;&lt;a href=&quot;#最小限のKubernetes-k8s-入門&quot; class=&quot;headerlink&quot; title=&quot;最小限のKubernetes(k8s)入門&quot;&gt;&lt;/a&gt;最小限のKubernetes(k8s)入門&lt;/h1&gt;&lt;h2 id=&quot;Kubernetesとは&quot;&gt;&lt;a href=&quot;#Kubernetesとは&quot; class=&quot;headerlink&quot; title=&quot;Kubernetesとは&quot;&gt;&lt;/a&gt;Kubernetesとは&lt;/h2&gt;&lt;p&gt;k8sはDocker Composeと同じく複数のコンテナを管理するためのツールです。&lt;/p&gt;
&lt;p&gt;Docker Composeでは一つのホスト上に複数のコンテナを起動することができましたが、
k8sでは複数のDockerサーバで一つのクラスタを構成し、その上に複数のコンテナを分散配置することができます。&lt;/p&gt;
&lt;p&gt;また、Docker Composeは単にコンテナの起動や停止だけでなく、コンテナ間通信やボリュームの管理を行うことができましたが、
k8sはさらにロードバランサや定期実行ジョブなど実環境で必要になる多様な機能を抽象化し管理することができます。&lt;/p&gt;
&lt;h2 id=&quot;minikubeとは&quot;&gt;&lt;a href=&quot;#minikubeとは&quot; class=&quot;headerlink&quot; title=&quot;minikubeとは&quot;&gt;&lt;/a&gt;minikubeとは&lt;/h2&gt;&lt;p&gt;k8sをセットアップして使えるようにするためには色々な方法があります。代表的なものをいくつか紹介します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GKE: GCP上のマネージドサービス&lt;/li&gt;
&lt;li&gt;kops: AWS上にec2インスタンスを複数起動してk8sクラスタとして構成するツール&lt;/li&gt;
&lt;li&gt;AKS: Azure上のマネージドサービス&lt;/li&gt;
&lt;li&gt;kubespray: オンプレミスを含む任意の環境にk8sクラスタをセットアップするツール&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本番環境でk8sを運用するためには上記のようなものを使う必要がありますが、
k8sを試してみるだけなら手元の環境で動作する&lt;a href=&quot;https://github.com/kubernetes/minikube&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;minikube&lt;/a&gt;を使うのが一番簡単です。&lt;/p&gt;
&lt;p&gt;minikubeは端末上のVMへk8sをインストールして簡単に使えるようにするためのツールです。
VMドライバとしてはVirtualBox、hyperkit、KVMなどがサポートされています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kubernetes/minikube/blob/master/docs/drivers.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;minikube/drivers.md&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;macOS上で試すのであれば VirtualBox か hyperkit が簡単です。
デフォルトでは VirtualBox が選択されるようですが、hyperkitの方が高速なのでこちらを使うのがおすすめです。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/moby/hyperkit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;hyperkit&lt;/a&gt;はmacOS組み込みの仮想化フレームワーク
「Hypervisor Framework」を使うためのツールで、Docker for Macでも使われています。&lt;/p&gt;
&lt;p&gt;次節以降ではminikubeの基本的な使い方を説明します。&lt;/p&gt;
    
    </summary>
    
      <category term="HowTo" scheme="https://chopschips.net/categories/HowTo/"/>
    
    
      <category term="rails" scheme="https://chopschips.net/tags/rails/"/>
    
      <category term="docker" scheme="https://chopschips.net/tags/docker/"/>
    
      <category term="kubernetes" scheme="https://chopschips.net/tags/kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>Railsアプリ開発のためのDocker/Kubernetes入門2 Docker Compose/Dockerfile編</title>
    <link href="https://chopschips.net/blog/2018/05/30/docker-compose-with-rails/"/>
    <id>https://chopschips.net/blog/2018/05/30/docker-compose-with-rails/</id>
    <published>2018-05-30T13:10:00.000Z</published>
    <updated>2018-05-30T15:39:54.255Z</updated>
    
    <content type="html"><![CDATA[<p>RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。この記事はシリーズ連載記事の第二回です。</p><ul><li>第一回 <a href="/blog/2018/05/30/docker-with-rails/">Docker編</a></li><li>第二回 <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a></li><li>第三回 <a href="/blog/2018/05/30/kubernetes-tutorial/">Kubernetes入門編</a></li><li>第四回 <a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes基礎編</a></li><li>第五回 <a href="/blog/2018/05/30/practical-kubernetes-with-rails/">Kubernetes応用編</a></li><li>第六回 <a href="/blog/2018/05/30/helm-with-rails/">Helm編</a></li></ul><p>今回は下記について書きます。</p><ul><li>最小限のDocker Compose入門</li><li>Docker Composeを使った各種ミドルウェアのインストールと管理</li><li>RailsアプリケーションのDockerイメージの作り方</li><li>Docker Composeによるローカルプレビュー環境の構築</li></ul><h1 id="サンプルアプリケーション"><a href="#サンプルアプリケーション" class="headerlink" title="サンプルアプリケーション"></a>サンプルアプリケーション</h1><p>簡単なRailsアプリを例に、Docker Composeの使い方やDockerfileの書き方を説明します。このサンプルアプリ<code>rails-k8s-demoapp</code>のコードは下記のリポジトリに置いています。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp" target="_blank" rel="noopener">https://github.com/kwhrtsk/rails-k8s-demoapp</a></p><p><code>rails-k8s-demoapp</code>は「フォームでメッセージを送信すると画面上のリストに追加して表示する」だけの小さなアプリです。</p><p>仕様は極小ですが、できるだけ一般的なRailsアプリの構成に近くなるように下記の要件を満たすものにしています。</p><ul><li>ミドルウェアとしてMySQLとRedis(SidekiqとActionCableのため)を使用</li><li>ActiveJobを使用: <strong>Pumaの他にSidekiqプロセスの起動が必要なケースを想定</strong><ul><li>新しいメッセージが追加されると、3秒後に逆順の文字列をさらにメッセージとして追加するようなジョブをSidekiqで実行します。</li></ul></li><li>ActionCableを使用: <strong>websocketが必要なケースを想定</strong><ul><li>新しいメッセージが追加された時にActionCableでブラウザに通知を行いページリロードさせます。</li></ul></li><li>Webpackerを使用: <strong>アセットのビルドにnodeとyarnが必要なケースを想定</strong><ul><li>フロントエンドのコードはTypeScriptで書いています。</li><li>webpackでBootstrap v4を組み込んでいます。</li></ul></li></ul><img src="/images/post_20180502_docker_compose_with_rails/rails-k8s-demoapp.gif"><p>下記のコマンドで動作を確認することができます。RubyやRailsの開発環境は必要ありません。gitとDockerがインストールされていれば動きます。</p><pre><code>$ 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/</code></pre><p><code>docker-compose</code>コマンドの使い方やサンプルアプリケーションで使っている<code>docker-compose.yml</code>と<code>Dockerfile</code>については順に説明します。</p><a id="more"></a><h1 id="最小限のDocker-Compose入門"><a href="#最小限のDocker-Compose入門" class="headerlink" title="最小限のDocker Compose入門"></a>最小限のDocker Compose入門</h1><h2 id="Docker-Composeとは"><a href="#Docker-Composeとは" class="headerlink" title="Docker Composeとは"></a>Docker Composeとは</h2><p>複数のコンテナをYAML形式の構成ファイルで一括管理するツールです。</p><ul><li>この構成ファイルを <strong>Composeファイル</strong> と呼びます。</li><li>通常、ファイル名は<code>docker-compose.yml</code>です。</li></ul><p>Railsアプリ開発において、Docker Composeには大きく二つの用途があります。</p><ol><li>開発環境におけるMySQLやRedisなどのミドルウェアのインストールと実行管理</li><li>完成したRailsアプリを(特に開発者以外が)簡単に手元で実行するための仕組み</li></ol><p>以降、それぞれの詳細について説明します。</p><h2 id="Docker-Composeを使った各種ミドルウェアのインストールと実行管理"><a href="#Docker-Composeを使った各種ミドルウェアのインストールと実行管理" class="headerlink" title="Docker Composeを使った各種ミドルウェアのインストールと実行管理"></a>Docker Composeを使った各種ミドルウェアのインストールと実行管理</h2><p>一般的なRailsアプリケーションでは、PostgreSQLやMySQLなどのRDBMSに加えて、しばしばRedisやElasticsearchなどのミドルウェアを使用します。従来、macOS上でRailsアプリの開発を行う場合にはこれらのミドルウェアのmacOS版をインストールし、サービスとして起動しておく必要があったため、READMEには長々とセットアップの手順を書いたりしていました。</p><p>一方、先に挙げたようなメジャーなプロダクトはいずれも公式のDockerイメージが存在するため、適切に書かれた <code>docker-compose.yml</code> ファイルさえあれば、<code>docker-compose up</code> コマンドを一つ実行するだけで必要なミドルウェアのイメージの取得からコンテナの起動まですべて自動で行うことができます。</p><p>また、コンテナとしてミドルウェアを起動する際には個別にバージョンやデータの保存場所を指定できるため、導入の手順が簡単になるだけでなく下記のようなメリットもあります。</p><ul><li>複数の開発プロジェクトを並行して進める際に、ミドルウェアの細かいバージョンや内部データを完全に独立して管理できる。</li><li>古いバージョンのミドルウェアを要求するようなプロジェクトの開発環境を維持しやすい。</li></ul><h2 id="Composeファイル-docker-compose-yml-のサンプル"><a href="#Composeファイル-docker-compose-yml-のサンプル" class="headerlink" title="Composeファイル(docker-compose.yml)のサンプル"></a>Composeファイル(docker-compose.yml)のサンプル</h2><p>前述のサンプルアプリケーションではMySQLとRedisを使います。この2つを起動するための<code>docker-compose.yml</code>は下記のような内容です。通常、このファイルはプロジェクトのルートディレクトリに置きます。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span><span class="token key atrule">services</span><span class="token punctuation">:</span>  <span class="token key atrule">mysql</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.21    <span class="token key atrule">environment</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> MYSQL_ROOT_PASSWORD=$MYSQL_PASSWORD    <span class="token key atrule">ports</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> 3306<span class="token punctuation">:</span><span class="token number">3306</span>    <span class="token key atrule">volumes</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> ./tmp/mysql<span class="token punctuation">:</span>/var/lib/mysql  <span class="token key atrule">redis</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> redis<span class="token punctuation">:</span>4.0.9    <span class="token key atrule">ports</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> 6379<span class="token punctuation">:</span><span class="token number">6379</span>    <span class="token key atrule">volumes</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> ./tmp/redis<span class="token punctuation">:</span>/data    <span class="token key atrule">command</span><span class="token punctuation">:</span> redis<span class="token punctuation">-</span>server <span class="token punctuation">-</span><span class="token punctuation">-</span>appendonly yes<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>services</code>の下にコンテナ(Docker Comoposeの文脈ではサービス)の定義を書いていきます。</p><p><code>mysql</code>については、前回<a href="/blog/2018/05/30/docker-with-rails/#docker-container-run-docker-run-%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%99%E3%82%8B-mysqld%E7%B7%A8">Docker編のdocker container run(mysql編)</a> に出てきた <code>docker container run</code>コマンドとやっていることはほぼ同じです。</p><ul><li><code>environment</code>: 環境変数を追加してコンテナを起動します。<ul><li><code>.envrc</code>に書かれている環境変数 <code>MYSQL_PASSWORD</code>をrootユーザのパスワードとして設定するため、<code>MYSQL_ROOT_PASSWORD</code>の値として設定しています。</li><li>ローカルの開発環境で作業を行う際には、<a href="https://github.com/direnv/direnv" target="_blank" rel="noopener">direnv</a>などのツールで<code>.envrc</code>の内容をシェルに設定するという想定です。</li><li><code>MYSQL_ALLOW_EMPTY_PASSWORD</code>に<code>yes</code>を設定すると<code>root</code>のパスワードを空にすることもできます。</li></ul></li><li><code>ports</code>: コンテナのポート(右側)をホスト側のポート(左側)にマッピングします。</li><li><code>volumes</code>: ホスト側のパス(左側)をコンテナ上のパス(右側)にマッピングします。<code>docker container run</code>の<code>-v</code>オプションとは異なり、ホスト側の相対パスで指定できます。</li></ul><p><code>redis</code>の<code>command</code>には、データをストレージに永続化するためのオプションを指定しています。</p><p>Composeファイルには多数の機能があります。詳細については下記のリファレンスを参照してください。</p><ul><li><a href="https://docs.docker.com/compose/compose-file/" target="_blank" rel="noopener">Compose file version 3 reference | Docker Documentation</a></li><li><a href="http://docs.docker.jp/compose/compose-file.html" target="_blank" rel="noopener">Compose ファイル・リファレンス | 日本語版 ドキュメント </a></li></ul><p><code>mysql</code>と<code>redis</code>のイメージに指定できる環境変数やコマンドのオプションについては、公式リポジトリのドキュメントを参照してください。</p><ul><li><a href="https://hub.docker.com/_/mysql/" target="_blank" rel="noopener">library/mysql - Docker Hub</a></li><li><a href="https://hub.docker.com/_/redis/" target="_blank" rel="noopener">library/redis - Docker Hub</a></li></ul><h2 id="docker-compose-up-各種ミドルウェアのインストールと起動"><a href="#docker-compose-up-各種ミドルウェアのインストールと起動" class="headerlink" title="docker-compose up: 各種ミドルウェアのインストールと起動"></a>docker-compose up: 各種ミドルウェアのインストールと起動</h2><p>まず、<code>.envrc</code>に書かれた環境変数を設定してください。</p><pre><code>$ source .envrc# direnvを使っている場合は下記でも可$ direnv allow</code></pre><p>下記のコマンドを実行すると、Docker Hubから<code>mysql</code>と<code>redis</code>のイメージを取得してコンテナが起動します。(<code>-d</code>はバックグラウンドで起動するという意味です)</p><p><code>-f</code>で任意のComposeファイルを指定できますが、省略するとカレントディレクトリの<code>docker-compose.yml</code>が参照されます。</p><pre><code>$ docker-compose up -dCreating network &quot;railsk8sdemoapp_default&quot; with the default driverPulling mysql (mysql:5.7.21)...5.7.21: Pulling from library/mysql2a72cbf407d6: Pull complete38680a9b47a8: Pull complete4c732aa0eb1b: Pull completec5317a34eddd: Pull completef92be680366c: Pull completee8ecd8bec5ab: Pull complete2a650284a6a8: Pull complete5b5108d08c6d: Pull completebeaff1261757: Pull completec1a55c6375b5: Pull complete8181cde51c65: Pull completeDigest: sha256:691c55aabb3c4e3b89b953dd2f022f7ea845e5443954767d321d5f5fa394e28cStatus: Downloaded newer image for mysql:5.7.21Pulling redis (redis:4.0.9)...4.0.9: Pulling from library/redisb0568b191983: Pull complete6637dc5b29fe: Pull complete7b4314315f15: Pull complete2fd86759b5ff: Pull complete0f04862b5a3b: Pull complete2db0056aa977: Pull completeDigest: sha256:6b9f935e89af002225c0dcdadf1fd74245b4cc1e3e91222f7e4769c236cf80d4Status: Downloaded newer image for redis:4.0.9Creating railsk8sdemoapp_redis_1 ... doneCreating railsk8sdemoapp_mysql_1 ... done</code></pre><p>2回目以降は取得済みのイメージを使用するため、起動は少し早くなります。</p><ul><li><a href="https://docs.docker.com/compose/reference/up/" target="_blank" rel="noopener">docker-compose up | Docker Documentation</a></li><li><a href="http://docs.docker.jp/compose/reference/up.html" target="_blank" rel="noopener">docker-compose up | 日本語版 ドキュメント</a></li></ul><p>MySQLとRedisが起動した後であれば、下記のように開発環境用の各種プロセスを起動できます。</p><pre><code>$ gem install foreman$ bundle install --path=vendor/bundle$ yarn install$ ./bin/rails db:setup$ foreman start -f Procfile</code></pre><p><a href="https://github.com/ddollar/foreman" target="_blank" rel="noopener">foreman</a>では、<code>puma</code>、<code>sidekiq</code>、<code>webpack-dev-server</code>の三つを起動します。(ruby, node, yarnが必要です)</p><pre class="line-numbers language-Procfile"><code class="language-Procfile"># Procfileweb: ./bin/rails s -p 3000worker: ./bin/sidekiqclient: ./bin/webpack-dev-server<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><!-- more --><h2 id="docker-compose-ps-コンテナの一覧を取得する"><a href="#docker-compose-ps-コンテナの一覧を取得する" class="headerlink" title="docker-compose ps: コンテナの一覧を取得する"></a>docker-compose ps: コンテナの一覧を取得する</h2><p><code>docker-compose.yml</code>に定義されたサービスのコンテナ一覧を表示します。</p><pre><code>$ docker-compose psName                        Command               State           Ports-----------------------------------------------------------------------------------------railsk8sdemoapp_mysql_1   docker-entrypoint.sh mysqld      Up      0.0.0.0:3306-&gt;3306/tcprailsk8sdemoapp_redis_1   docker-entrypoint.sh redis ...   Up      0.0.0.0:6379-&gt;6379/tcp</code></pre><p>コンテナの名前は、<code>${プレフィクス}_${サービス名}_${連番}</code>になります。プレフィクスはカレントディレクトリから<code>-</code>や<code>_</code>を除いた文字列になります。連番がついているのは一つのサービスに複数のコンテナが起動し得るためです。(今回は紹介しませんが、<code>docker-compose scale</code>コマンドでサービスごとのコンテナの数を増減させることができます)</p><ul><li><a href="https://docs.docker.com/compose/reference/ps/" target="_blank" rel="noopener">docker-compose ps | Docker Documentation</a></li><li><a href="http://docs.docker.jp/compose/reference/ps.html" target="_blank" rel="noopener">docker-compose ps | 日本語版 ドキュメント</a></li></ul><h2 id="docker-compose-down-コンテナの削除"><a href="#docker-compose-down-コンテナの削除" class="headerlink" title="docker-compose down: コンテナの削除"></a>docker-compose down: コンテナの削除</h2><p>下記のコマンドを実行すると、<code>docker-compose.yml</code>に書かれたすべてのサービスのコンテナを停止した後、削除します。<code>-v</code>オプションは関連するデータボリュームも削除するという意味です。(ホスト側のボリュームをマウントしている場合は残ります)</p><pre><code>$ docker-compose down -vStopping railsk8sdemoapp_mysql_1 ... doneStopping railsk8sdemoapp_redis_1 ... doneRemoving railsk8sdemoapp_mysql_1 ... doneRemoving railsk8sdemoapp_redis_1 ... doneRemoving network railsk8sdemoapp_default</code></pre><ul><li><a href="https://docs.docker.com/compose/reference/down/" target="_blank" rel="noopener">docker-compose down | Docker Documentation</a></li><li><a href="http://docs.docker.jp/compose/reference/down.html" target="_blank" rel="noopener">docker-compose down | 日本語版 ドキュメント</a></li></ul><h1 id="最小限のDockerfile入門"><a href="#最小限のDockerfile入門" class="headerlink" title="最小限のDockerfile入門"></a>最小限のDockerfile入門</h1><p>RailsアプリのDockerイメージを作る方法を説明します。</p><p>Dockerイメージを作るには、まず<code>Dockerfile</code>にイメージの作り方を記述します。</p><p>サンプルアプリ <a href="https://github.com/kwhrtsk/rails-k8s-demoapp" target="_blank" rel="noopener">rails-k8s-demoapp</a> に同梱している下記のような<code>Dockerfile</code>を例に説明します。</p><pre class="line-numbers language-docker"><code class="language-docker"><span class="token comment" spellcheck="true">### image for build</span><span class="token keyword">FROM</span> ruby<span class="token punctuation">:</span>2.5.1<span class="token punctuation">-</span>alpine AS build<span class="token punctuation">-</span>envARG RAILS_ROOT=/appARG BUILD_PACKAGES=<span class="token string">"build-base curl-dev git"</span>ARG DEV_PACKAGES=<span class="token string">"libxml2-dev libxslt-dev mysql-dev yaml-dev zlib-dev nodejs yarn"</span>ARG RUBY_PACKAGES=<span class="token string">"tzdata yaml"</span><span class="token keyword">ENV</span> BUNDLE_APP_CONFIG=<span class="token string">"$RAILS_ROOT/.bundle"</span><span class="token keyword">WORKDIR</span> $RAILS_ROOT<span class="token comment" spellcheck="true"># install packages</span><span class="token keyword">RUN</span> apk update \ &amp;&amp; apk upgrade \ &amp;&amp; apk add <span class="token punctuation">-</span><span class="token punctuation">-</span>update <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>cache $BUILD_PACKAGES $DEV_PACKAGES $RUBY_PACKAGES<span class="token comment" spellcheck="true"># install rubygem</span><span class="token keyword">COPY</span> Gemfile Gemfile.lock $RAILS_ROOT/<span class="token keyword">RUN</span> bundle install <span class="token punctuation">-</span>j4 <span class="token punctuation">-</span><span class="token punctuation">-</span>path=vendor/bundle<span class="token comment" spellcheck="true"># install npm</span><span class="token keyword">COPY</span> package.json yarn.lock $RAILS_ROOT/<span class="token keyword">RUN</span> yarn install<span class="token comment" spellcheck="true"># build assets</span><span class="token keyword">COPY</span> . $RAILS_ROOT<span class="token keyword">RUN</span> bundle exec rake webpacker<span class="token punctuation">:</span>compile<span class="token keyword">RUN</span> bundle exec rake assets<span class="token punctuation">:</span>precompile<span class="token comment" spellcheck="true">### image for execution</span><span class="token keyword">FROM</span> ruby<span class="token punctuation">:</span>2.5.1<span class="token punctuation">-</span>alpine<span class="token keyword">LABEL</span> <span class="token keyword">maintainer</span> <span class="token string">'Kawahara Taisuke &lt;kwhrtsk@gmail.com>'</span>ARG RAILS_ROOT=/appARG PACKAGES=<span class="token string">"tzdata yaml mariadb-client-libs bash"</span><span class="token keyword">ENV</span> RAILS_ENV=production<span class="token keyword">ENV</span> BUNDLE_APP_CONFIG=<span class="token string">"$RAILS_ROOT/.bundle"</span><span class="token keyword">WORKDIR</span> $RAILS_ROOT<span class="token comment" spellcheck="true"># install packages</span><span class="token keyword">RUN</span> apk update \ &amp;&amp; apk upgrade \ &amp;&amp; apk add <span class="token punctuation">-</span><span class="token punctuation">-</span>update <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>cache $PACKAGES<span class="token keyword">COPY</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>from=build<span class="token punctuation">-</span>env $RAILS_ROOT $RAILS_ROOT<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>Dockerfile</code>では、上記の例のように<code>FROM</code>や<code>RUN</code>などのコマンドを一行に一つ書きます。各コマンドの詳細は下記のリファレンスを参照してください。<code>docker image build</code> コマンドでイメージの作成を行うと、<code>Dockerfile</code> に書かれたコマンドが上から順に実行されます。</p><ul><li><a href="https://docs.docker.com/engine/reference/builder/" target="_blank" rel="noopener">Dockerfile reference | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/builder.html" target="_blank" rel="noopener">Dockerfile リファレンス | 日本語版 ドキュメント</a></li></ul><p>この<code>Dockerfile</code>で<code>rails-k8s-demoapp</code>のイメージをビルドするには下記のようにします。</p><pre><code>$ git clone https://github.com/kwhrtsk/rails-k8s-demoapp.git$ cd rails-k8s-demoapp$ docker image build . -t demoapp:latest</code></pre><p><code>.</code>はビルドコンテキストです。通常、<code>Dockerfile</code>が置いてあるパスを指定します。<code>-t</code>はイメージの名前です。</p><ul><li><a href="https://docs.docker.com/engine/reference/commandline/image_build/" target="_blank" rel="noopener">docker image build | Docker Documentation</a></li><li>旧コマンド名は <code>docker build</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/build/" target="_blank" rel="noopener">docker build | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/build.html" target="_blank" rel="noopener">docker build | 日本語版 ドキュメント</a></li></ul></li></ul><p>これでRailsアプリのイメージができました。下記のコマンドでコンテナを起動できます。</p><pre><code>$ docker container run -it --rm demoapp:latest lsGemfile                     libGemfile.lock                logProcfile                    node_modulesREADME.md                   package.jsonRakefile                    publicapp                         specbin                         storageconfig                      testconfig.ru                   tmpdb                          tsconfig.jsondocker-compose-preview.yml  vendordocker-compose.yml          yarn.lockk8s</code></pre><p>この例では<code>ls</code>コマンドを実行してコンテナのファイルを表示しています。上記のようにアプリケーションルートディレクトリの中身が表示されるはずです。</p><p>次に<code>Dockerfile</code>の中身を順に解説していきます。</p><h2 id="FROM"><a href="#FROM" class="headerlink" title="FROM"></a>FROM</h2><p><code>FROM</code>コマンドではベースイメージを指定します。サンプルの<code>Dockerfile</code>では2回出てきますが、このケースでは2つイメージを作っています。</p><pre class="line-numbers language-docker"><code class="language-docker"><span class="token keyword">FROM</span> ruby<span class="token punctuation">:</span>2.5.1<span class="token punctuation">-</span>alpine AS build<span class="token punctuation">-</span>env<span class="token comment" spellcheck="true"># ...</span><span class="token keyword">FROM</span> ruby<span class="token punctuation">:</span>2.5.1<span class="token punctuation">-</span>alpine<span class="token comment" spellcheck="true"># ...</span><span class="token keyword">COPY</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>from=build<span class="token punctuation">-</span>env $RAILS_ROOT $RAILS_ROOT<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>前半がgemやnpmのC拡張やjsやcssなどのアセットの <strong>ビルド用イメージ</strong> で、後半がアプリケーションとして運用する <strong>実行用イメージ</strong> です。後半では前半のビルドの結果を単にコピーしています。このように2段階に分けてイメージを作成している理由は、イメージのサイズを小さくするためです。詳細は <a href="#レイヤについて">レイヤについて</a> で説明します。</p><p>最終的なイメージの成果物は後者の <strong>実行用イメージ</strong> です。前者は削除しても構いませんが、残しておくと2回目以降のビルドが差分で実行されるため速くなります(これについても後述)。</p><p>次にベースイメージの<code>ruby:2.5.1-alpine</code>について説明します。<a href="https://hub.docker.com/_/ruby/" target="_blank" rel="noopener">Docker Hubのrubyリポジトリ</a>には大まかに3系統のタグがあります。</p><ul><li><code>ruby:&lt;version&gt;</code>: Debian stretchベース</li><li><code>ruby:slim</code>: Debian stretchベースだがインストールされたパッケージが少ない</li><li><code>ruby:alpine</code>: Alpine Linuxベース</li></ul><p>今回はイメージサイズが最も小さい<code>ruby:2.5.1-alpine</code>を使います。</p><pre><code>% docker imagesruby                2.5.1-alpine        b620ae34414c        9 days ago           55.5MBruby                2.5.1-slim          85b814a932e6        9 days ago           172MBruby                2.5.1               1624ebb80e3e        9 days ago           863MB</code></pre><p>また、Alpine Linuxのパッケージマネージャである<code>apk</code>はDebianの<code>apt</code>と比べてNode.jsやYARNのインストールやキャッシュの制御がより簡単というメリットもあります。</p><ul><li><a href="https://docs.docker.com/engine/reference/builder/#from" target="_blank" rel="noopener">FROM | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/builder.html#from" target="_blank" rel="noopener">FROM | 日本語版 ドキュメント</a></li></ul><h2 id="ENV-ARG"><a href="#ENV-ARG" class="headerlink" title="ENV, ARG"></a>ENV, ARG</h2><p><code>ENV</code>と<code>ARG</code>はどちらもコンテナに環境変数を設定するコマンドですが、<code>ARG</code>で指定した環境変数はイメージのビルド時にだけ設定され、作成済みのイメージをコンテナ化した際には残っていないという特徴があります。また、<code>docker image build</code>コマンドの<code>--build-arg</code>や<code>docker-compose.yml</code>の<code>args</code>オプションで上書きすることができます。例えば、プロキシ環境下でイメージをビルドする際に<code>HTTP_PROXY</code>のような環境変数を指定する際には、<code>ENV</code>ではなく<code>ARG</code>を使うのが望ましいです。</p><p>このサンプルではインストールするパッケージの名前などを<code>ARG</code>で設定しています。また、<code>ENV</code>では環境変数 <code>RAILS_ENV</code> を <code>production</code> に設定しています。</p><pre class="line-numbers language-docker"><code class="language-docker"><span class="token comment" spellcheck="true">### image for build</span><span class="token comment" spellcheck="true"># ...</span><span class="token comment" spellcheck="true"># アプリケーションのインストール先</span>ARG RAILS_ROOT=/app<span class="token comment" spellcheck="true"># gemやnpmのC拡張やjs, cssなどのアセットのビルドに必要なパッケージ</span>ARG BUILD_PACKAGES=<span class="token string">"build-base curl-dev git"</span>ARG DEV_PACKAGES=<span class="token string">"libxml2-dev libxslt-dev mysql-dev yaml-dev zlib-dev nodejs yarn"</span>ARG RUBY_PACKAGES=<span class="token string">"tzdata yaml"</span><span class="token keyword">ENV</span> BUNDLE_APP_CONFIG=<span class="token string">"$RAILS_ROOT/.bundle"</span><span class="token comment" spellcheck="true">### image for execution</span><span class="token comment" spellcheck="true"># ...</span><span class="token comment" spellcheck="true"># Railsアプリの実行に必要なパッケージ</span>ARG PACKAGES=<span class="token string">"tzdata yaml mariadb-client-libs bash"</span><span class="token keyword">ENV</span> RAILS_ENV=production<span class="token keyword">ENV</span> BUNDLE_APP_CONFIG=<span class="token string">"$RAILS_ROOT/.bundle"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>BUNDLE_APP_CONFIG</code>は<a href="https://hub.docker.com/_/ruby/" target="_blank" rel="noopener">rubyイメージ</a>がもともと持っている環境変数です。この<code>Dockerfile</code>では、ビルド済みのbundleディレクトリを丸ごと実行イメージにコピーしたいので、gemのインストール先を<code>${RAILS_ROOT}/vendor/bundle</code>に指定しています。このようにインストール先を変更した場合には <code>BUNDLE_APP_CONFIG</code> を上記のように上書きしないと<code>bundle exec</code>が正常に動作しません。</p><p>ただし、この挙動は紛らわしいという指摘もあるため、将来の更新で修正されるかもしれません。</p><p><a href="https://github.com/docker-library/ruby/issues/129" target="_blank" rel="noopener">introduction of BUNDLE_APP_CONFIG leads to unexpected behavior for ‘statically’ bundled apps Issue #129 · docker-library/ruby</a></p><ul><li><a href="https://docs.docker.com/engine/reference/builder/#env" target="_blank" rel="noopener">ENV | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/builder.html#env" target="_blank" rel="noopener">ENV | 日本語版 ドキュメント</a></li><li><a href="https://docs.docker.com/engine/reference/builder/#arg" target="_blank" rel="noopener">ARG | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/builder.html#arg" target="_blank" rel="noopener">ARG | 日本語版 ドキュメント</a></li></ul><h2 id="WORKDIR"><a href="#WORKDIR" class="headerlink" title="WORKDIR"></a>WORKDIR</h2><p><code>WORKDIR</code>はこのイメージから作成したコンテナ上でコマンドを実行するときのカレントディレクトリです。この例では <code>/app</code> をRailsアプリのルートディレクトリとして指定しているので、<code>WORKDIR</code>も同じパスにしています。</p><pre class="line-numbers language-docker"><code class="language-docker">ARG RAILS_ROOT=/app<span class="token keyword">WORKDIR</span> $RAILS_ROOT<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>また、<code>RUN</code>や<code>COPY</code>などの<code>Dockerfile</code>のコマンドも<code>WORKDIR</code>で指定したパスで実行されます。</p><ul><li><a href="https://docs.docker.com/engine/reference/builder/#workdir" target="_blank" rel="noopener">WORKDIR | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/builder.html#workdir" target="_blank" rel="noopener">WORKDIR | 日本語版 ドキュメント</a></li></ul><h2 id="RUN-COPY"><a href="#RUN-COPY" class="headerlink" title="RUN, COPY"></a>RUN, COPY</h2><p><code>RUN</code>はコマンドを実行します。主にパッケージのインストールやアプリケーションのビルドなどを行います。</p><p><code>COPY</code>はホスト側のファイルをコンテナ側に複製します。この時、<code>.dockerignore</code>ファイルで指定されたファイルは複製されません。パスワードやクレデンシャルのような秘匿値を書いたファイルは忘れずに<code>.dockerignore</code>に追加してください。また、<code>.dockerignore</code>に一致したファイルはイメージのビルド時に<code>dockerd</code>へ転送されなくなるため、<code>.git</code>や<code>node_modules</code>などビルド時に不要でかつサイズやファイル数が大きいディレクトリも指定するのがセオリーです。</p><p>今回ベースイメージにしているのは<code>ruby:2.5.1-alpine</code>というイメージですが、これはAlpine Linuxというディストリビューションをベースにしています。Alpine Linuxでは<code>apk</code>というパッケージマネージャを使います。使用可能なパッケージを下記のサイトで検索できます。<a href="https://pkgs.alpinelinux.org/packages" target="_blank" rel="noopener">Alpine Linux packages</a></p><pre class="line-numbers language-docker"><code class="language-docker"><span class="token keyword">RUN</span> apk update \ &amp;&amp; apk upgrade \ &amp;&amp; apk add <span class="token punctuation">-</span><span class="token punctuation">-</span>update <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>cache $BUILD_PACKAGES $DEV_PACKAGES $RUBY_PACKAGES<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li><a href="https://docs.docker.com/engine/reference/builder/#run" target="_blank" rel="noopener">RUN | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/builder.html#run" target="_blank" rel="noopener">RUN | 日本語版 ドキュメント</a></li><li><a href="https://docs.docker.com/engine/reference/builder/#copy" target="_blank" rel="noopener">COPY | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/builder.html#copy" target="_blank" rel="noopener">COPY | 日本語版 ドキュメント</a></li><li><a href="https://docs.docker.com/engine/reference/builder/#dockerignore-file" target="_blank" rel="noopener">.dockerignore | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/builder.html#dockerignore-file" target="_blank" rel="noopener">.dockerignore | 日本語版 ドキュメント</a></li></ul><h2 id="レイヤについて"><a href="#レイヤについて" class="headerlink" title="レイヤについて"></a>レイヤについて</h2><p>Dockerイメージのデータはレイヤと呼ばれる単位で記録されており、<code>Dockerfile</code>の<code>RUN</code>や<code>COPY</code>などのコマンドは実行するたびに新しいレイヤに結果が記録されます。またイメージのビルドや送受信はレイヤ単位でキャッシュされて差分実行されるため、適切な単位でレイヤを分割しなければビルドやデプロイに無駄な時間がかかるようになります。特に、一度追加したファイルは別のレイヤで削除したとしても以前のレイヤに残り続けるため、イメージ全体のファイルサイズは減らない点に注意が必要です。</p><p>一般的に、<code>Dockerfile</code>ではイメージのファイルサイズを減らしたりビルドやデプロイの速度を上げるために次のような工夫をします。</p><ul><li><code>Gemfile</code>、<code>Gemfile.lock</code>のコピーと<code>bundle install</code>の実行はアプリケーションコードの<code>COPY</code>より前で個別に行う。</li></ul><p>gemの追加は比較的頻度の少ない作業なので、こうしておくと<code>bundle install</code>の頻度を減らしてビルドを高速化できます。<code>package.json</code>と<code>yarn.lock</code>のコピー、<code>yarn</code>の実行についても同様です。</p><ul><li>ビルド用のイメージと実行用のイメージを分けて、実行時に必要なファイルだけをビルド用のイメージから実行用のイメージにコピーする。</li></ul><p>このようにすることでビルドに必要なパッケージが含まれるレイヤを丸ごと削除できます。</p><p>「1つの<code>RUN</code>コマンドでパッケージのインストール、ビルド、パッケージやキャッシュの削除を行う」ことでも同じことを実現できますが、パッケージのインストールとビルドのレイヤが同じになるので、アプリのコードを更新しただけでもビルド時にはパッケージのインストールからやり直しになり、余分に時間がかかるようになります。<code>COPY</code>コマンドの<code>--from</code>オプションは比較的最近追加された機能なので、古いドキュメントにはよくこのようなやり方が書いてあります(古いランタイムに配慮して止むを得ずこのような実装になっているケースもあるかもしれません)。</p><h1 id="docker-composeコマンドによるローカルプレビュー環境"><a href="#docker-composeコマンドによるローカルプレビュー環境" class="headerlink" title="docker-composeコマンドによるローカルプレビュー環境"></a>docker-composeコマンドによるローカルプレビュー環境</h1><p>RailsアプリのDockerイメージを作れるようになったので、次は「Ruby/Railsの開発環境がなくても、DockerさえあればRailsアプリのイメージを作成して起動できるようにする」ためのComposeファイルを用意します。</p><p>ローカルでの開発用に <code>docker-compose.yml</code> がすでにあるので、<code>docker-compose-preview.yml</code> というファイル名で用意します。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span><span class="token key atrule">services</span><span class="token punctuation">:</span>  <span class="token key atrule">puma</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">build</span><span class="token punctuation">:</span>      <span class="token key atrule">context</span><span class="token punctuation">:</span> .    <span class="token key atrule">env_file</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> .dockerenv/rails    <span class="token key atrule">ports</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> 3000<span class="token punctuation">:</span><span class="token number">3000</span>    <span class="token key atrule">depends_on</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> mysql      <span class="token punctuation">-</span> redis    <span class="token key atrule">command</span><span class="token punctuation">:</span> ./bin/setup<span class="token punctuation">-</span>db<span class="token punctuation">-</span>and<span class="token punctuation">-</span>start<span class="token punctuation">-</span>puma  <span class="token key atrule">sidekiq</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> demoapp    <span class="token key atrule">env_file</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> .dockerenv/rails    <span class="token key atrule">depends_on</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> puma    <span class="token key atrule">command</span><span class="token punctuation">:</span> ./bin/start<span class="token punctuation">-</span>sidekiq  <span class="token key atrule">mysql</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.21    <span class="token key atrule">env_file</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> .dockerenv/mysql  <span class="token key atrule">redis</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> redis<span class="token punctuation">:</span>4.0.9    <span class="token key atrule">command</span><span class="token punctuation">:</span> redis<span class="token punctuation">-</span>server <span class="token punctuation">-</span><span class="token punctuation">-</span>appendonly yes<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>コードをチェックアウトした後、下記のコマンドを実行するだけでRailsアプリが <a href="http://localhost:3000/" target="_blank" rel="noopener">http://localhost:3000/</a> に起動するようになります。</p><pre><code>$ docker-compose -f docker-compose-preview.yml up -d</code></pre><p>ログを見たい場合は<code>-d</code>オプションを外すか、下記のようにしてください。</p><pre><code>$ docker-compose -f docker-compose-preview.yml logs -f</code></pre><p>後始末は下記のようにします。<code>-v</code>を外すとデータボリュームが残ってしまうので注意してください。</p><pre><code>$ docker-compose -f docker-compose-preview.yml down -v</code></pre><p>このYAMLファイルのポイントは下記の通り。</p><ul><li><code>mysql</code>と<code>redis</code>には<code>puma</code>と<code>sidekiq</code>のコンテナから接続できれば良いので、<code>ports</code>(ホスト側へのポートマッピング)エントリを書きません。<ul><li>コンテナ同士は特に設定をしなくても互いのポートにアクセスできます。</li></ul></li><li><code>puma</code>には<code>build</code>エントリを追加して、<code>docker-compose up</code>実行時にイメージをビルドするようにしています。<ul><li>2回目以降、イメージを強制的にビルドする際には <code>--build</code> オプションをつける必要があります。</li></ul></li><li><code>puma</code>は<code>depends_on</code>で<code>mysql</code>を指定して、<code>mysql</code>コンテナの起動後に<code>rake db:setup</code>が実行されるようにしています。<ul><li>ただしこれだけだと不十分なので<code>nc</code>で<code>mysql</code>の3306番ポートを確認して起動するまでループします。後述。</li></ul></li><li><code>puma</code>、<code>sidekiq</code>、<code>mysql</code>には<code>env_file</code>を指定し、共通の環境変数をファイルで一括指定します。後述。</li></ul><p>pumaとsidekiqの<code>command</code>では、<code>./bin/setup-db-start-puma</code>, <code>./bin/start-sidekiq</code>というコマンドをそれぞれ指定しています。これは下記のような内容です。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># ./bin/setup-db-and-start-puma</span><span class="token function">cd</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">dirname</span> $0<span class="token variable">)</span></span>/<span class="token punctuation">..</span><span class="token function">trap</span> <span class="token string">"pkill -P $$"</span> EXIT./bin/wait-for <span class="token variable">$MYSQL_HOST</span> 3306./bin/wait-for <span class="token variable">$REDIS_HOST</span> 6379./bin/rails db:setup_if_not_yet./bin/pumactl start<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># ./bin/start-sidekiq</span><span class="token function">cd</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">dirname</span> $0<span class="token variable">)</span></span>/<span class="token punctuation">..</span><span class="token function">trap</span> <span class="token string">"pkill -P $$"</span> EXIT./bin/wait-for <span class="token variable">$MYSQL_HOST</span> 3306./bin/wait-for <span class="token variable">$REDIS_HOST</span> 6379./bin/sidekiq -t <span class="token variable">${SIDEKIQ_TIMEOUT:-8}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>pumaやsidekiqの起動をシェルスクリプトでラップする場合、ラッパ側のシェルがTERMやINTなどのシグナルで停止すると子プロセスのpumaやsidekiqが起動したまま残ってしまいます。上記の<code>trap</code>はそれを防ぐための記述で、スクリプトの停止時に子プロセスへTERMシグナルを送るように指定しています。</p><p>sidekiqの<code>-t</code>オプションは実行中のジョブを強制的に停止するまでのタイムアウト値で、オプションを指定しない場合のデフォルト値は8秒です。このスクリプトでは環境変数<code>SIDEKIQ_TIMEOUT</code>でこの値を変更できるようにしています。環境変数が存在しない場合はデフォルト値と同じ8秒が指定されます。</p><p><code>./bin/wait-for</code> は下記のような内容で、パラメータで指定したホストのポートを<code>nc</code>コマンドでチェックして接続可能になるまで待機するだけのスクリプトです。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># ./bin/wait-for</span>HOST<span class="token operator">=</span><span class="token variable">$1</span>PORT<span class="token operator">=</span><span class="token variable">$2</span><span class="token keyword">while</span> <span class="token keyword">:</span><span class="token keyword">do</span>  nc -w 1 -z <span class="token variable">$HOST</span> <span class="token variable">$PORT</span>  <span class="token keyword">if</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token variable">$?</span> <span class="token operator">=</span> 0 <span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>    <span class="token keyword">break</span><span class="token punctuation">;</span>  <span class="token keyword">fi</span>  <span class="token function">sleep</span> 1<span class="token keyword">done</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>db:setup_if_not_yet</code>は下記のようなRakeタスクで、まだ<code>rake db:setup</code>を実行したことがなさそうな時だけ実行します。</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token comment" spellcheck="true"># lib/tasks/db.rake</span>namespace <span class="token symbol">:db</span> <span class="token keyword">do</span>  task setup_if_not_yet<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token symbol">:environment</span><span class="token punctuation">]</span> <span class="token keyword">do</span>    <span class="token keyword">begin</span>      <span class="token constant">ActiveRecord</span><span class="token punctuation">:</span><span class="token symbol">:Base</span><span class="token punctuation">.</span>connection    <span class="token keyword">rescue</span> <span class="token constant">ActiveRecord</span><span class="token punctuation">:</span><span class="token symbol">:NoDatabaseError</span>      <span class="token comment" spellcheck="true"># database not exists</span>      <span class="token constant">Rake</span><span class="token punctuation">:</span><span class="token symbol">:Task</span><span class="token punctuation">[</span><span class="token string">"db:setup"</span><span class="token punctuation">]</span><span class="token punctuation">.</span>invoke      exit <span class="token number">0</span>    <span class="token keyword">else</span>      <span class="token keyword">if</span> <span class="token operator">!</span><span class="token constant">ActiveRecord</span><span class="token punctuation">:</span><span class="token symbol">:SchemaMigration</span><span class="token punctuation">.</span>table_exists<span class="token operator">?</span>        <span class="token comment" spellcheck="true"># database exists but tables not exists</span>        <span class="token constant">Rake</span><span class="token punctuation">:</span><span class="token symbol">:Task</span><span class="token punctuation">[</span><span class="token string">"db:setup"</span><span class="token punctuation">]</span><span class="token punctuation">.</span>invoke        exit <span class="token number">0</span>      <span class="token keyword">end</span>    <span class="token keyword">end</span>  <span class="token keyword">end</span><span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>.dockerenv/rails</code>は<code>puma</code>と<code>sidekiq</code>に共通の環境変数の設定ファイルです。</p><pre><code>RAILS_SERVE_STATIC_FILES=trueRAILS_LOG_TO_STDOUT=trueSIDEKIQ_TIMEOUT=60SECRET_KEY_BASE=123MYSQL_HOST=mysqlMYSQL_USER=demoappMYSQL_PASSWORD=secretMYSQL_DATABASE=demoapp_productionREDIS_HOST=redisREDIS_URL=redis://redis:6379/1</code></pre><p><code>.dockerenv/mysql</code>は<code>mysql</code>用の環境変数の設定ファイルです。デーベース名、ユーザ名、パスワードを書いています。</p><pre><code>MYSQL_USER=demoappMYSQL_PASSWORD=secretMYSQL_DATABASE=demoapp_productionMYSQL_ROOT_PASSWORD=topsecret</code></pre><p><a href="https://hub.docker.com/_/mysql/" target="_blank" rel="noopener">公式のmysqlイメージ</a>では、rootユーザのパスワードの他、コンテナの起動時に作成するデータベースとそのデータベースにアクセスできるユーザとパスワードも環境変数で指定できます。<code>.dockerenv/mysql</code>で設定している4つの環境変数はそのためのものです。</p><p>また、Railsアプリ側ではMySQLやRedisの接続先など実行環境に依存するようなパラメータを全て環境変数で受け取れるようにしておく必要があります。</p><p>まずデータベースの設定ファイルでは、<code>MYSQL_HOST</code>, <code>MYSQL_DATABASE</code>, <code>MYSQL_USER</code>, <code>MYSQL_PASSWORD</code>でそれぞれDBのホスト名、データベース名、ユーザ名、パスワードを受け取れるようにしておきます。(話を簡単にするため、<code>mysql</code>コンテナと環境変数の名前を合わせています)</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># config/database.yml</span><span class="token key atrule">default</span><span class="token punctuation">:</span> <span class="token important">&amp;default</span>  <span class="token key atrule">adapter</span><span class="token punctuation">:</span> mysql2  <span class="token key atrule">encoding</span><span class="token punctuation">:</span> utf8  <span class="token key atrule">pool</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("RAILS_MAX_THREADS") <span class="token punctuation">{</span> <span class="token number">5 </span><span class="token punctuation">}</span> %<span class="token punctuation">></span>  <span class="token key atrule">username</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("MYSQL_USER") <span class="token punctuation">{</span> <span class="token string">"root"</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span>  <span class="token key atrule">password</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("MYSQL_PASSWORD") <span class="token punctuation">{</span> <span class="token string">""</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span>  <span class="token key atrule">host</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("MYSQL_HOST") <span class="token punctuation">{</span> <span class="token string">"0.0.0.0"</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span><span class="token key atrule">development</span><span class="token punctuation">:</span>  <span class="token key atrule">&lt;&lt;</span><span class="token punctuation">:</span> <span class="token important">*default</span>  <span class="token key atrule">database</span><span class="token punctuation">:</span> demoapp_development<span class="token key atrule">test</span><span class="token punctuation">:</span>  <span class="token key atrule">&lt;&lt;</span><span class="token punctuation">:</span> <span class="token important">*default</span>  <span class="token key atrule">database</span><span class="token punctuation">:</span> demoapp_test<span class="token key atrule">production</span><span class="token punctuation">:</span>  <span class="token key atrule">&lt;&lt;</span><span class="token punctuation">:</span> <span class="token important">*default</span>  <span class="token key atrule">database</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("MYSQL_DATABASE") <span class="token punctuation">{</span> <span class="token string">"demoapp_production"</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>また、ActionCableの設定ファイルでは環境変数 <code>REDIS_URL</code> でRedisの接続先を指定できるようにしておきます。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># config/cable.yml</span><span class="token key atrule">development</span><span class="token punctuation">:</span>  <span class="token key atrule">adapter</span><span class="token punctuation">:</span> redis  <span class="token key atrule">url</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("REDIS_URL") <span class="token punctuation">{</span> <span class="token string">"redis://localhost:6379/1"</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span><span class="token key atrule">test</span><span class="token punctuation">:</span>  <span class="token key atrule">adapter</span><span class="token punctuation">:</span> async<span class="token key atrule">production</span><span class="token punctuation">:</span>  <span class="token key atrule">adapter</span><span class="token punctuation">:</span> redis  <span class="token key atrule">url</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("REDIS_URL") <span class="token punctuation">{</span> <span class="token string">"redis://localhost:6379/1"</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span>  <span class="token key atrule">channel_prefix</span><span class="token punctuation">:</span> demoapp_production<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Sidekiqについてはもともと環境変数 <code>REDIS_URL</code> で接続先を切り替えられる仕様なので特に何もしなくて良いです。別の方法で接続先を指定したい場合は <code>config/initializers/sidekiq.rb</code> というファイルを作って設定を追加します。詳細は下記のドキュメントを参照してください。</p><p><a href="https://github.com/mperham/sidekiq/wiki/Using-Redis" target="_blank" rel="noopener">Using Redis mperham/sidekiq Wiki</a></p><p><code>REDIS_HOST</code>は前述の<code>bin/wait-for</code>に渡すパラメータとして使っています。</p><p><code>SECRET_KEY_BASE</code>はCookieに改ざん検知のためのHMACダイジェストをつけるときの秘密鍵を指定するための環境変数ですが、Rails 5.2.0のデフォルトの動作では<a href="https://github.com/rails/rails/pull/30067" target="_blank" rel="noopener">Credentials</a>で暗号化されたファイル <code>config/credentials.yml.enc</code> に書かれた値を鍵として使うようになっています。下記のコマンドでこのファイルの中身を確認できます。</p><pre><code>% ./bin/rails credentials:show# aws:#   access_key_id: 123#   secret_access_key: 345# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.secret_key_base: f758dcb894cf0fee1b68f7989ebd65f5fd0dbb3c366e61789c112836789dfda22def856b6419ae96447352bbdd8873b90ae0096cd61d9532775caca8c04e0783</code></pre><p>ただ、この値は本番環境でも使う値になるので、今回のような不特定多数に配布するのが前提のプレビュー環境には使えません。HMAC用の鍵はCredentials の値よりも環境変数<code>SECRET_KEY_BASE</code>の値が優先される実装になっているので、今回は環境変数を設定します。また、Credentialsの秘密鍵(環境変数<code>RAILS_MASTER_KEY</code>または<code>config/master.key</code>)は設定しません。(デフォルトでは設定しなくてもエラーにはなりません。)</p><p><code>RAILS_SERVE_STATIC_FILES</code>は、jsやcssなどのファイルをpumaが応答するかどうかを指定するための設定値です。これはRails標準の仕組みで、実装は <code>config/environments/production.rb</code> を参照してください。本番環境ではこのような静的なアセットファイルはnginxやCDNで配信するのが望ましいのですが、今回は構成を簡単にするためにtrueにしておきます。</p><p><code>RAILS_LOG_TO_STDOUT</code>は、<code>production</code>環境でRailsのログ出力先を標準入力に切り替えるための環境変数です。これもRails標準の仕組みで、実装は <code>config/environments/production.rb</code> を参照してください。</p><p>こういった環境変数設定用のファイルに本番環境の値を書く場合には、GitリポジトリやDockerイメージに含めないように注意してください。<code>.gitignore</code> と <code>.dockerignore</code> にそれらのファイル名を書いておくと意図せず混入させることを防ぐことができます。</p><p>本稿では、<code>docker-compose-preview.yml</code>で構築するのはあくまでプレビュー用の環境であり、本番環境はKubernetesで構築するという前提です。Kubernetes上のアプリに環境変数を設定する際には全く別の方法を用いるため、Composeファイルなどに書いた設定値は全て本番環境とは異なる値という想定なので、リポジトリにコミットしています。</p><p>また、<code>env_file</code>で指定するファイルは、<code>direnv</code>で使う<code>.envrc</code>などシェルに環境変数を設定するスクリプトとは根本的に異なるものなので下記の点に注意してください。</p><ul><li><code>export</code>はつけない。</li><li>コマンドは実行できない。</li><li>値をクオートしてはいけない。(環境変数に<code>&#39;</code>や<code>&quot;</code>を含む形で設定されてしまいます)</li></ul><h2 id="コンテナ間の通信について"><a href="#コンテナ間の通信について" class="headerlink" title="コンテナ間の通信について"></a>コンテナ間の通信について</h2><p>コンテナにはそれぞれ固有のIPアドレスが割り当てられます。<code>docker container run</code>コマンドで起動したコンテナ同士が互いのIPアドレスを知るためにはオプション指定が必要ですが、<code>docker-compose</code>で起動したコンテナ同士は、互いのサービス名をホスト名として通信できるように自動的に設定されます。また、ホスト側からコンテナに接続する際には<code>ports</code>エントリでポートマッピングの設定を書く必要がありましたが、<code>docker-compose</code>で起動したコンテナ同士はこういった設定なしで互いの全てのポートにアクセスできます。</p><p>MySQLの接続設定をもう一度掲載します。下記のように<code>MYSQL_HOST</code>環境変数でホスト名を指定するようになっています。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># config/database.yml</span><span class="token key atrule">default</span><span class="token punctuation">:</span> <span class="token important">&amp;default</span>  <span class="token key atrule">adapter</span><span class="token punctuation">:</span> mysql2  <span class="token key atrule">encoding</span><span class="token punctuation">:</span> utf8  <span class="token key atrule">pool</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("RAILS_MAX_THREADS") <span class="token punctuation">{</span> <span class="token number">5 </span><span class="token punctuation">}</span> %<span class="token punctuation">></span>  <span class="token key atrule">username</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("MYSQL_USER") <span class="token punctuation">{</span> <span class="token string">"root"</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span>  <span class="token key atrule">password</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("MYSQL_PASSWORD") <span class="token punctuation">{</span> <span class="token string">""</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span>  <span class="token key atrule">host</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("MYSQL_HOST") <span class="token punctuation">{</span> <span class="token string">"0.0.0.0"</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span><span class="token comment" spellcheck="true"># 省略</span><span class="token key atrule">production</span><span class="token punctuation">:</span>  <span class="token key atrule">&lt;&lt;</span><span class="token punctuation">:</span> <span class="token important">*default</span>  <span class="token key atrule">database</span><span class="token punctuation">:</span> &lt;%= ENV.fetch("MYSQL_DATABASE") <span class="token punctuation">{</span> <span class="token string">"demoapp_production"</span> <span class="token punctuation">}</span> %<span class="token punctuation">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>.dockerenv/rails</code>では下記のように<code>mysql</code>という文字列を<code>MYSQL_HOST</code>環境変数に設定していました。</p><pre><code>MYSQL_HOST=mysql</code></pre><p>この<code>mysql</code>というのは、<code>docker-compose-preview.yml</code>のサービス名を指しています。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span><span class="token key atrule">services</span><span class="token punctuation">:</span>  <span class="token comment" spellcheck="true"># 省略</span>  <span class="token key atrule">mysql</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># &lt;- これがサービス名</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.21  <span class="token comment" spellcheck="true"># 省略</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>docker container run</code>コマンドに<code>--link</code>オプションをつけると<code>docker-compose</code>が自動的に準備してくれていたようなことを手動で行うこともできますが、Railsアプリ開発という主題においては必要になる場面はほぼ無いと思いますので、詳細は割愛します。</p><h1 id="ローカル開発環境のRailsアプリをDocker化するかどうか"><a href="#ローカル開発環境のRailsアプリをDocker化するかどうか" class="headerlink" title="ローカル開発環境のRailsアプリをDocker化するかどうか"></a>ローカル開発環境のRailsアプリをDocker化するかどうか</h1><p>少なくともホストOSがmacOSなのであれば、ローカルの開発環境においてはRailsアプリまでDockerコンテナの上で動かす必要はないと筆者は考えています。</p><ul><li>RailsアプリがmacOSで動いてLinux(コンテナ)だと動かないケースは多くない。</li><li>多くの場合、macOS上でRailsアプリの開発環境を用意するのはそれほど面倒ではない。</li><li><code>./bin/rails g</code>や<code>./bin/rails c</code>などちょっとしたコマンドが全て<code>docker</code>コマンド経由になるのは煩雑すぎる。</li><li>springの対応が面倒</li></ul><p>以上の理由で、私の主観ではメリットをデメリットが上回っていると感じるので、開発環境においては下記のような構成をとっています。</p><ul><li>Railsプロセス自体はホスト側(macOS/Linux)で直接起動する。</li><li>Railsプロセスが接続するMySQLやRedisなどのミドルウェアは<code>docker-compsoe</code>で起動する。</li></ul><h1 id="参考情報"><a href="#参考情報" class="headerlink" title="参考情報"></a>参考情報</h1><p>Docker Composeのより詳細な使い方は下記のドキュメントを参照してください。</p><ul><li><code>docker-compose</code>コマンドについて<ul><li><a href="https://docs.docker.com/compose/reference/overview/" target="_blank" rel="noopener">Overview of docker-compose CLI | Docker Documentation</a></li><li><a href="http://docs.docker.jp/compose/reference/index.html#" target="_blank" rel="noopener">Compose CLI リファレンス | 日本語版 ドキュメント</a></li></ul></li><li>Composeファイル(<code>docker-compose.yml</code>)について<ul><li><a href="https://docs.docker.com/compose/compose-file/" target="_blank" rel="noopener">Compose file version 3 reference | Docker Documentation</a></li><li><a href="http://docs.docker.jp/compose/compose-file.html" target="_blank" rel="noopener">Compose ファイル・リファレンス | 日本語版 ドキュメント</a></li></ul></li></ul><p>Dockerfileのより詳細な仕様については下記のドキュメントを参照してください。</p><ul><li><a href="https://docs.docker.com/engine/reference/builder/" target="_blank" rel="noopener">Dockerfile reference | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/builder.html" target="_blank" rel="noopener">Dockerfile リファレンス | 日本語版 ドキュメント</a></li></ul><p>おすすめの書籍は「プログラマのためのDocker教科書」です。2018/4/11に第2版が出ました。</p><div style="display: flex;"><iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="https://rcm-fe.amazon-adsystem.com/e/cm?ref=tf_til&t=chopschips03-22&m=amazon&o=9&p=8&l=as1&IS1=1&detail=1&asins=4798153222&linkId=db46cec2f20263827bcad4a1cd4c7b7f&bc1=000000&lt1=_blank&fc1=333333&lc1=0066c0&bg1=ffffff&f=ifr"></iframe></div><p>前回の<a href="/blog/2018/05/30/docker-with-rails/">Docker編</a>で扱った範囲も含めて、本ドキュメントでは紹介しなかったコマンドやDockerfile/Composeファイルの機能が一通り紹介されています。また、コンテナ技術の概要や使いどころ、プライベートレジストリやイメージの公開方法などDocker周辺を広く浅く解説してあり、入門には良い書籍だと思います。後半ではKubernetesにも軽く触れてあります。</p><p>次回は <a href="/blog/2018/05/30/kubernetes-tutorial/">Kubernetes入門編</a> です。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;RailsアプリケーションをKubernetes(以後、k8s)で運用できるようにするための手順を書きます。
この記事はシリーズ連載記事の第二回です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一回 &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/&quot;&gt;Docker編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第二回 &lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第三回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-tutorial/&quot;&gt;Kubernetes入門編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第四回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-with-rails/&quot;&gt;Kubernetes基礎編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第五回 &lt;a href=&quot;/blog/2018/05/30/practical-kubernetes-with-rails/&quot;&gt;Kubernetes応用編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第六回 &lt;a href=&quot;/blog/2018/05/30/helm-with-rails/&quot;&gt;Helm編&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回は下記について書きます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最小限のDocker Compose入門&lt;/li&gt;
&lt;li&gt;Docker Composeを使った各種ミドルウェアのインストールと管理&lt;/li&gt;
&lt;li&gt;RailsアプリケーションのDockerイメージの作り方&lt;/li&gt;
&lt;li&gt;Docker Composeによるローカルプレビュー環境の構築&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;サンプルアプリケーション&quot;&gt;&lt;a href=&quot;#サンプルアプリケーション&quot; class=&quot;headerlink&quot; title=&quot;サンプルアプリケーション&quot;&gt;&lt;/a&gt;サンプルアプリケーション&lt;/h1&gt;&lt;p&gt;簡単なRailsアプリを例に、Docker Composeの使い方やDockerfileの書き方を説明します。
このサンプルアプリ&lt;code&gt;rails-k8s-demoapp&lt;/code&gt;のコードは下記のリポジトリに置いています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kwhrtsk/rails-k8s-demoapp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kwhrtsk/rails-k8s-demoapp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rails-k8s-demoapp&lt;/code&gt;は「フォームでメッセージを送信すると画面上のリストに追加して表示する」だけの小さなアプリです。&lt;/p&gt;
&lt;p&gt;仕様は極小ですが、できるだけ一般的なRailsアプリの構成に近くなるように下記の要件を満たすものにしています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ミドルウェアとしてMySQLとRedis(SidekiqとActionCableのため)を使用&lt;/li&gt;
&lt;li&gt;ActiveJobを使用: &lt;strong&gt;Pumaの他にSidekiqプロセスの起動が必要なケースを想定&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;新しいメッセージが追加されると、3秒後に逆順の文字列をさらにメッセージとして追加するようなジョブをSidekiqで実行します。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ActionCableを使用: &lt;strong&gt;websocketが必要なケースを想定&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;新しいメッセージが追加された時にActionCableでブラウザに通知を行いページリロードさせます。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Webpackerを使用: &lt;strong&gt;アセットのビルドにnodeとyarnが必要なケースを想定&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;フロントエンドのコードはTypeScriptで書いています。&lt;/li&gt;
&lt;li&gt;webpackでBootstrap v4を組み込んでいます。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;img src=&quot;/images/post_20180502_docker_compose_with_rails/rails-k8s-demoapp.gif&quot;&gt;
&lt;p&gt;下記のコマンドで動作を確認することができます。RubyやRailsの開発環境は必要ありません。
gitとDockerがインストールされていれば動きます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ 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/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;docker-compose&lt;/code&gt;コマンドの使い方やサンプルアプリケーションで使っている&lt;code&gt;docker-compose.yml&lt;/code&gt;と&lt;code&gt;Dockerfile&lt;/code&gt;については順に説明します。&lt;/p&gt;
    
    </summary>
    
      <category term="HowTo" scheme="https://chopschips.net/categories/HowTo/"/>
    
    
      <category term="rails" scheme="https://chopschips.net/tags/rails/"/>
    
      <category term="docker" scheme="https://chopschips.net/tags/docker/"/>
    
      <category term="kubernetes" scheme="https://chopschips.net/tags/kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>Railsアプリ開発のためのDocker/Kubernetes入門1 Docker編</title>
    <link href="https://chopschips.net/blog/2018/05/30/docker-with-rails/"/>
    <id>https://chopschips.net/blog/2018/05/30/docker-with-rails/</id>
    <published>2018-05-30T13:00:00.000Z</published>
    <updated>2018-05-30T14:15:47.715Z</updated>
    
    <content type="html"><![CDATA[<p>RailsアプリケーションをKubernetes(k8s)で運用できるようにするための手順を書きます。</p><h1 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h1><p>この記事はシリーズ連載記事の第一回です。シリーズ全体の概要は次の通りです。</p><p>はじめに、DockerやKubernetesの概要について簡単に説明しつつ、<code>docker</code>コマンドや<code>kubectl</code>コマンドの基本的な使い方をチュートリアル風味で紹介します。</p><p>次に、ごく小さな仕様の具体的なRailsアプリを題材として、Docker Composeの構成ファイルやKubernetesのマニフェストの具体的なサンプルと使い方を示し、最終的にHelmを使ってKubernetesクラスタにデプロイする方法を説明します。</p><p>DockerとKubernetesの全機能を網羅的に学ぼうとすると膨大な時間が必要になるので、この記事では「RailsアプリをKubernetesで運用すること」にフォーカスして一つ一つの概念や機能の詳細は必要最小限に絞り、より詳細な資料へのポインタを置くに留めます。</p><p>シリーズ全体の構成は次の通りです。</p><ul><li>第一回 <a href="/blog/2018/05/30/docker-with-rails/">Docker編</a><ul><li>最小限のDocker入門</li><li><code>docker</code>コマンドのチュートリアル</li></ul></li><li>第二回 <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a><ul><li>最小限のDocker Compose入門</li><li>Docker Composeを使った各種ミドルウェアのインストールと管理</li><li>RailsアプリケーションのDockerイメージの作り方</li><li>Docker Composeによるローカルプレビュー環境の構築</li></ul></li><li>第三回 <a href="/blog/2018/05/30/kubernetes-tutorial/">Kubernetes入門編</a><ul><li>最小限のk8s入門</li><li>minikubeの使い方</li><li><code>kubectl</code>コマンドのチュートリアル</li></ul></li><li>第四回 <a href="/blog/2018/05/30/kubernetes-with-rails/">Kubernetes基礎編</a><ul><li><code>kubectl</code>コマンドとYAML形式のマニフェストファイルでRailsアプリをk8sにデプロイする方法</li></ul></li><li>第五回 <a href="/blog/2018/05/30/practical-kubernetes-with-rails/">Kubernetes応用編</a><ul><li>基礎編で残した課題の解決編</li></ul></li><li>第六回 <a href="/blog/2018/05/30/helm-with-rails/">Helm編</a><ul><li>最小限のHelm入門</li><li>HelmでRailsアプリをk8sにデプロイする方法</li></ul></li></ul><p>全てのサンプルコードはここにあります。</p><p><a href="https://github.com/kwhrtsk/rails-k8s-demoapp" target="_blank" rel="noopener">https://github.com/kwhrtsk/rails-k8s-demoapp</a></p><h1 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h1><ul><li>各種ツール類のインストール手順はmacOSでの作業を前提としています。</li><li><a href="https://docs.brew.sh/Installation" target="_blank" rel="noopener">Homebrew</a>と<a href="https://caskroom.github.io/" target="_blank" rel="noopener">Homebrew-Cask</a>を使います。<ul><li>Homebrew-Caskのインストールは<code>brew tap caskroom/cask</code></li></ul></li><li>Docker for Macを使います。<ul><li><a href="https://store.docker.com/editions/community/docker-ce-desktop-mac" target="_blank" rel="noopener">Docker Community Edition for Mac</a>のGet Dockerをクリック</li></ul></li><li>k8s関連ツールのインストール手順は後述します。</li><li>ツールのインストール手順を除けば、Linuxでもおおよそ同じ手順で動作を確認できると思います。</li></ul><p>それぞれ下記のバージョンで動作を確認しています。</p><table><thead><tr><th>名称</th><th>バージョン</th></tr></thead><tbody><tr><td>macOS</td><td>High Sierra(10.13.4)</td></tr><tr><td>Docker for Mac</td><td>18.0.3.1-ce-mac65</td></tr><tr><td>Ruby</td><td>2.5.1</td></tr><tr><td>Rails</td><td>5.2.0</td></tr><tr><td>minikube</td><td>0.27.0 (k8s 1.10.0)</td></tr><tr><td>kubectl</td><td>1.10.2</td></tr><tr><td>helm</td><td>2.9.1</td></tr><tr><td>hyperkit</td><td>v0.20171204-60-g0e5b6b</td></tr><tr><td>virtualbox</td><td>5.2.8</td></tr></tbody></table><h1 id="最小限のDocker入門"><a href="#最小限のDocker入門" class="headerlink" title="最小限のDocker入門"></a>最小限のDocker入門</h1><p>Docker初心者がRailsアプリをk8sで動かすまでに最低限必要なDockerについての知識と<code>docker</code>コマンドの操作方法を書きます。Rails固有のトピックを知りたい人は <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a> までスキップしてください。</p><h2 id="Dockerとは"><a href="#Dockerとは" class="headerlink" title="Dockerとは"></a>Dockerとは</h2><p>LXC(Linux Containers)と呼ばれる技術を使ってアプリケーションの開発やデプロイを行うためのツール及びプラットフォームです。LXCやDockerについて解説されたドキュメントは山ほどあるのでここでは詳細は割愛します。</p><ul><li><a href="https://knowledge.sakura.ad.jp/2108/" target="_blank" rel="noopener">15分で分かるLXC（Linux Containers）の仕組みと基本的な使い方 | さくらのナレッジ</a></li><li><a href="http://www.atmarkit.co.jp/ait/articles/1701/30/news037.html" target="_blank" rel="noopener">第1回 Dockerとは : 超入門Docker - ＠IT</a></li></ul><h2 id="dockerのコマンド体系について"><a href="#dockerのコマンド体系について" class="headerlink" title="dockerのコマンド体系について"></a>dockerのコマンド体系について</h2><p>2017年1月にリリースされたDocker 1.13で <code>docker</code> コマンドの構成が整理されました。アナウンスは <a href="https://blog.docker.com/2017/01/whats-new-in-docker-1-13/" target="_blank" rel="noopener">Introducing Docker 1.13 - Docker Blog</a>の<code>CLI restructured</code>の部分です。</p><p>「旧コマンドは引き続きサポートするが、新コマンドの使用を勧める」とのことなのでこのドキュメントでは新しいコマンド体系で説明します。ただ、古いドキュメントには旧コマンド体系で書かれているものも多いため、対応する旧コマンドについても紹介します。</p><p>このドキュメントで紹介するコマンドの新旧対応は下記の通りです。基本的にはimageかcontainerのサブコマンドへの移動ですが、一部違うものがあるので太字にしています。</p><table><thead><tr><th>旧コマンド</th><th>新コマンド</th><th>説明</th></tr></thead><tbody><tr><td>docker pull</td><td>docker image pull</td><td>イメージを取得する</td></tr><tr><td>docker <strong>images</strong></td><td>docker image <strong>ls</strong></td><td>イメージの一覧を表示する</td></tr><tr><td>docker rmi</td><td>docker image <strong>rm</strong></td><td>イメージを削除する</td></tr><tr><td>docker run</td><td>docker container run</td><td>コンテナを起動する</td></tr><tr><td>docker <strong>ps</strong></td><td>docker container <strong>ls</strong></td><td>コンテナの一覧を表示する</td></tr><tr><td>docker exec</td><td>docker container exec</td><td>起動中のコンテナで新しいコマンドを実行する</td></tr><tr><td>docker logs</td><td>docker container logs</td><td>コンテナのログを表示する</td></tr><tr><td>docker rm</td><td>docker container rm</td><td>コンテナを削除する</td></tr></tbody></table><p>以降の節で順にこれらのコマンドの使い方を説明します。見出しの括弧は旧コマンドです。</p><p>また、各節の終わりにリファレンスへのリンクを置いていますが、少なくとも本稿の執筆時点においては旧コマンドの方が詳細に書かれているので、必要に応じてそちらも参照してください。なお日本語版のドキュメントにはまだ新コマンド版のリファレンスはありません。</p><h2 id="シェルの補完について"><a href="#シェルの補完について" class="headerlink" title="シェルの補完について"></a>シェルの補完について</h2><p><code>docker</code>のサブコマンドは、 <code>861162a44</code> のようなハッシュ値や、<code>romantic_neumann</code> のようにランダムな英単語の組み合わせで自動生成されたコンテナ名をパラメータとして受け取ります。</p><p>Docker for Macにはbashやzshでコマンドの補完を行うためのスクリプトが同梱されていますが、ただインストールするだけでは有効になりません。補完のための設定については別に記事を書いたのでこちらを参照してください。</p><p><a href="https://chopschips.net/blog/2018/04/17/docker-completion/">bash/zshとfzfでDocker関連コマンドの補完を行う方法</a></p><h2 id="docker-image-pull-docker-pull-イメージを取得する"><a href="#docker-image-pull-docker-pull-イメージを取得する" class="headerlink" title="docker image pull (docker pull): イメージを取得する"></a>docker image pull (docker pull): イメージを取得する</h2><p>イメージとはコンテナの雛形です。<code>docker image pull</code>コマンドでリポジトリとタグを指定し、イメージを取得します。MySQLの公式リポジトリから<code>5.7.21</code>のタグが付いたイメージを取得する場合は下記のようにします。</p><pre><code>$ docker image pull mysql:5.7.215.7.21: Pulling from library/mysql2a72cbf407d6: Pull complete38680a9b47a8: Pull complete4c732aa0eb1b: Pull completec5317a34eddd: Pull completef92be680366c: Pull completee8ecd8bec5ab: Pull complete2a650284a6a8: Pull complete5b5108d08c6d: Pull completebeaff1261757: Pull completec1a55c6375b5: Pull complete8181cde51c65: Pull completeDigest: sha256:691c55aabb3c4e3b89b953dd2f022f7ea845e5443954767d321d5f5fa394e28cStatus: Downloaded newer image for mysql:5.7.21</code></pre><p>タグは、多くの場合バージョンを指します。ただし、ここでいうバージョンはイメージのバージョンではなく、アプリケーションのバージョンであることが多いため、同じタグを指定してもタイミングによって違うイメージを取得するケースがある点に注意が必要です。</p><p>例えば<code>mysql:5.7</code>は現時点では<code>mysql:5.7.21</code>と同じイメージを指していますが、以前は<code>mysql:5.7.20</code>を指していました。タグを指定しないと<code>latest</code>というタグを指定したとみなされます。</p><p>リポジトリを管理しているサービスをレジストリと呼びます。MySQLやRedisなどの主要なプロダクトの公式イメージは Docker Hub というレジストリで配布されており、上記の例ではDocker Hub上のリポジトリからイメージを取得しています。Docker Hub上のリポジトリは下記のページで探すことができます。</p><p><a href="https://hub.docker.com/explore/" target="_blank" rel="noopener">https://hub.docker.com/explore/</a></p><p>Docker Hub以外のレジストリを使う場合は下記のようにリポジトリの前にレジストリのホスト名とポート番号を書きます。例えばローカルレジストリ <code>myregistry.local:5000</code> から <code>testing/test-image</code> を取得する場合は下記のようになります。</p><pre><code>$ docker image pull myregistry.local:5000/testing/test-image</code></pre><ul><li><a href="https://docs.docker.com/engine/reference/commandline/image_pull/" target="_blank" rel="noopener">docker image pull | Docker Documentation</a></li><li>旧コマンド名は <code>docker pull</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/pull/" target="_blank" rel="noopener">docker pull | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/pull.html" target="_blank" rel="noopener">docker pull | 日本語版 ドキュメント</a></li></ul></li></ul><a id="more"></a><h2 id="docker-image-ls-docker-images-イメージの一覧を表示する"><a href="#docker-image-ls-docker-images-イメージの一覧を表示する" class="headerlink" title="docker image ls (docker images): イメージの一覧を表示する"></a>docker image ls (docker images): イメージの一覧を表示する</h2><p>持っているイメージの一覧を表示します。</p><pre><code>$ docker image lsREPOSITORY          TAG                 IMAGE ID            CREATED             SIZEmysql               5.7.21              5195076672a7        3 weeks ago         371MB</code></pre><ul><li><a href="https://docs.docker.com/engine/reference/commandline/image_ls/" target="_blank" rel="noopener">docker image ls | Docker Documentation</a></li><li>旧コマンド名は <code>docker images</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/images/" target="_blank" rel="noopener">docker images | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/images.html" target="_blank" rel="noopener">docker images | 日本語版 ドキュメント</a></li></ul></li></ul><h2 id="docker-image-rm-docker-rmi-イメージを削除する"><a href="#docker-image-rm-docker-rmi-イメージを削除する" class="headerlink" title="docker image rm (docker rmi): イメージを削除する"></a>docker image rm (docker rmi): イメージを削除する</h2><p><code>docker image ls</code> で表示された <code>IMAGE ID</code> を指定します。</p><pre><code>$ docker image rm 5195076672a7Untagged: mysql:5.7.21Untagged: mysql@sha256:691c55aabb3c4e3b89b953dd2f022f7ea845e5443954767d321d5f5fa394e28cDeleted: sha256:5195076672a7e30525705a18f7d352c920bbd07a5ae72b30e374081fe660a011Deleted: sha256:bc52f6d08bc65c22baab4384ae534d4c5ba8c988197de49975e0a0f78310dd89Deleted: sha256:b2590548a0917767b420cf20d0cef3aae8912314de216f624c0840f3ad827aa7Deleted: sha256:756d63a7d5896b52d445ea84ee392cb08a7c119322cfcdfed6303de1ed0d0eabDeleted: sha256:8e4736576db75536185beba95c5877deeb3915740688cbbc17fe04aed3632282Deleted: sha256:e6e6e1bb8a16eadbe6628770767615fbc8d67bf11dde69a902116efe847baa7eDeleted: sha256:080b6c4ec1d55d91a7087e12ae3bd4df252148d94f9911209e0a83d50dc63784Deleted: sha256:58b97da9f98f75af01ae59c3cb1fdd07a07297015459f3f9f88b140699b29147Deleted: sha256:3918448e7fe95f36f67a55c938559bab787249b8fa5c7e9914afd46994d045b0Deleted: sha256:fac8373d1ec4f5bb6c13f12170f558edc3cfbfe8215ae3d1c869940401bc14cfDeleted: sha256:130f3e567e288fdbbc3ae7cd7aa6c8b3d952bebd3eae58f0a7da93acbb22a258Deleted: sha256:3358360aedad76edf49d0022818228d959d20a4cccc55d01c32f8b62e226e2c2</code></pre><ul><li><a href="https://docs.docker.com/engine/reference/commandline/image_rm/" target="_blank" rel="noopener">docker image rm | Docker Documentation</a></li><li>旧コマンド名は <code>docker rmi</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/rmi/" target="_blank" rel="noopener">docker rmi | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/rmi.html" target="_blank" rel="noopener">docker rmi | 日本語版 ドキュメント</a></li></ul></li></ul><h2 id="docker-container-run-docker-run-コンテナを起動する-シェル編"><a href="#docker-container-run-docker-run-コンテナを起動する-シェル編" class="headerlink" title="docker container run (docker run): コンテナを起動する(シェル編)"></a>docker container run (docker run): コンテナを起動する(シェル編)</h2><p><code>docker container run</code>コマンドを使います。下記のようなフォーマットです。</p><pre><code>$ docker container run [OPTIONS] IMAGE [COMMAND] [ARGS...]</code></pre><p>指定したIMAGEで新しいコンテナを起動し、COMMANDを実行します。ARGSはCOMMANDに対する引数です。</p><p>COMMANDを省略した場合は、そのイメージに定義されたデフォルトのコマンドが実行されます。</p><p><code>mysql:5.7.21</code> イメージでbashを実行する場合は下記のようにします。</p><pre><code>$ docker container run -it --rm mysql:5.7.21 /bin/bash</code></pre><p>オプションはそれぞれ下記のような意味です。</p><ul><li><code>-it</code>: シェルのような対話型のCLIコマンドを起動する場合に指定します。</li><li><code>--rm</code>: プロセス終了時にコンテナを削除したい場合に指定します。(指定しないとコンテナの残骸が残ります)</li></ul><p><code>-i</code>と<code>-t</code>は別々のオプションですが、セットで指定することが多いオプションです。詳細は<a href="http://docs.docker.jp/engine/reference/commandline/run.html#tty-name-it" target="_blank" rel="noopener">リファレンス</a>で確認してください。</p><p>なお、指定したイメージがない場合は自動的にレジストリからダウンロードされるため、事前に<code>docker image pull</code>を実行する必要はありません。</p><ul><li><a href="https://docs.docker.com/engine/reference/commandline/container_run/" target="_blank" rel="noopener">docker container run | Docker Documentation</a></li><li>旧コマンド名は <code>docker run</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/run/" target="_blank" rel="noopener">docker run | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/run.html" target="_blank" rel="noopener">docker run | 日本語版 ドキュメント</a></li></ul></li></ul><h2 id="docker-container-run-docker-run-コンテナを起動する-mysqld編"><a href="#docker-container-run-docker-run-コンテナを起動する-mysqld編" class="headerlink" title="docker container run (docker run): コンテナを起動する(mysqld編)"></a>docker container run (docker run): コンテナを起動する(mysqld編)</h2><p><code>mysql:5.7.21</code> イメージでコンテナを起動し、ローカルから接続してみます。</p><pre><code>$ docker container run --rm -d -p 3306:3306 -v $(pwd)/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mysql --name db1 mysql:5.7.2140a551e33878d12c0c2fff330e6af0ad487be2235919a49ebc29dd4c084ddcf1</code></pre><p>表示されたのは<code>CONTAINER ID</code>です。後述するコンテナへの操作を行う際には、このIDか<code>--name</code>で指定した名前を指定します。今回はCOMMANDを指定していないので、自動的に<code>mysqld</code>コマンドが実行されます。<code>mysql</code>イメージの内容については <a href="https://hub.docker.com/_/mysql/" target="_blank" rel="noopener">Docker Hubのmysqlのページ</a>にあるDockerfileへのリンクで確認できます。</p><p>オプションはそれぞれ下記のような意味です。</p><ul><li><code>--rm</code>: プロセス終了時にコンテナを削除したい場合に指定します。(指定しないとコンテナの残骸が残ります)</li><li><code>-d</code>: コンテナをバックグラウンドで起動します。</li><li><code>-p 3306:3306</code>: コンテナのポートをホスト側のポートにマッピングします。左側がホスト側のポートです。</li><li><code>-v $(pwd)/data:/var/lib/mysql</code>: ホスト側のパスをコンテナ上のパスにマッピングします。左側がホスト側のパスで、絶対パスで指定する必要があります。</li><li><code>-e MYSQL_ROOT_PASSWORD=mysql</code>: 環境変数を追加してコンテナを起動します。</li><li><code>--name db1</code>: コンテナの名前です。省略するとランダムな値が自動的に与えられます。</li></ul><p><code>MYSQL_ROOT_PASSWORD</code>のように、イメージによっては環境変数を指定することでコンテナの動作を変えることができる場合があります。<code>mysql</code>イメージにおける環境変数の仕様は <a href="https://hub.docker.com/_/mysql/" target="_blank" rel="noopener">このページ</a>の<code>Environment Variables</code>以下に書かれています。大体の場合はDocker Hubのリポジトリのページに説明があります。</p><p><code>-v</code>オプションは、<code>Data Volume</code>の指定に使います。コンテナを削除するとコンテナに書き込まれたデータは全て削除されますが、データベースのようなプロダクトではそれだと困るので、永続化データをコンテナの外部に持つ仕組み(Data Volume)があります。今回のケースではホスト側のカレントディレクトリ以下の<code>data/</code>というディレクトリをコンテナ上の<code>/var/lib/mysql/</code>にマウントしています。<code>mysql</code>イメージのデフォルト設定では<code>/var/lib/mysql/</code>はMySQLのデータディレクトリなので、ホスト側で<code>data/</code>ディレクトリを削除しない限り、コンテナを削除しても同じパスにマウントしてまたコンテナを起動すればデータベースの内容を維持できます。Data Volumeの詳細については下記のドキュメントを参照してください。</p><ul><li><a href="http://docs.docker.jp/engine/userguide/storagedriver/imagesandcontainers.html#data-volumes-and-the-storage-driver" target="_blank" rel="noopener">イメージ、コンテナ、ストレージ・ドライバの理解 — Docker-docs-ja 17.06.Beta ドキュメント</a></li><li><a href="https://docs.docker.com/storage/volumes/#create-and-manage-volumes" target="_blank" rel="noopener">Use volumes | Docker Documentation</a></li></ul><p>なお、起動したコンテナ上のmysqldには下記のコマンドで接続できます。(mysqlコマンドがない場合、<code>brew install mysql</code>でインストールできます)</p><pre><code>$ mysql --host 127.0.0.1 -uroot -pmysql</code></pre><ul><li><a href="https://docs.docker.com/engine/reference/commandline/container_run/" target="_blank" rel="noopener">docker container run | Docker Documentation</a></li><li>旧コマンド名は <code>docker run</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/run/" target="_blank" rel="noopener">docker run | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/run.html" target="_blank" rel="noopener">docker run | 日本語版 ドキュメント</a></li></ul></li></ul><h2 id="docker-container-ls-docker-ps-コンテナの一覧を取得する"><a href="#docker-container-ls-docker-ps-コンテナの一覧を取得する" class="headerlink" title="docker container ls (docker ps): コンテナの一覧を取得する"></a>docker container ls (docker ps): コンテナの一覧を取得する</h2><pre><code>$ docker container ls -aCONTAINER ID        IMAGE               COMMAND                  CREATED                  STATUS              PORTS                    NAMES40a551e33878        mysql:5.7.21        &quot;docker-entrypoint.s…&quot;   Less than a second ago   Up 2 seconds        0.0.0.0:3306-&gt;3306/tcp   db1</code></pre><p><code>-a</code> オプションは停止中のコンテナも表示するという意味です。</p><ul><li><a href="https://docs.docker.com/engine/reference/commandline/container_ls/" target="_blank" rel="noopener">docker container ls | Docker Documentation</a></li><li>旧コマンド名は <code>docker ps</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/ps/" target="_blank" rel="noopener">docker ps | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/ps.html" target="_blank" rel="noopener">docker ps | 日本語版 ドキュメント</a></li></ul></li></ul><h2 id="docker-container-exec-docker-exec-起動中のコンテナで別のコマンドを実行する"><a href="#docker-container-exec-docker-exec-起動中のコンテナで別のコマンドを実行する" class="headerlink" title="docker container exec (docker exec): 起動中のコンテナで別のコマンドを実行する"></a>docker container exec (docker exec): 起動中のコンテナで別のコマンドを実行する</h2><p><code>CONTAINER ID</code>または<code>docker container run</code>でコンテナを起動するときに<code>--name</code>オプションで指定した名前でコンテナを指定します。以下の例では、先ほどバックグラウンドで起動したmysqlコンテナでbashを起動し、ターミナルからmysqladminコマンドを実行しています。オプションの <code>-it</code> の意味は <code>docker container run</code> と同じです。</p><pre><code>% docker container exec -it db1 /bin/bashroot@40a551e33878:/# mysqladmin status -pmysqlmysqladmin: [Warning] Using a password on the command line interface can be insecure.Uptime: 43  Threads: 1  Questions: 4  Slow queries: 0  Opens: 105  Flush tables: 1  Open tables: 98  Queries per second avg: 0.093root@40a551e33878:/# exit</code></pre><ul><li><a href="https://docs.docker.com/engine/reference/commandline/container_exec/" target="_blank" rel="noopener">docker container exec | Docker Documentation</a></li><li>旧コマンド名は <code>docker exec</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/exec/" target="_blank" rel="noopener">docker exec | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/exec.html" target="_blank" rel="noopener">docker exec | 日本語版 ドキュメント</a></li></ul></li></ul><h2 id="docker-container-logs-docker-logs-コンテナのログを取得する"><a href="#docker-container-logs-docker-logs-コンテナのログを取得する" class="headerlink" title="docker container logs (docker logs): コンテナのログを取得する"></a>docker container logs (docker logs): コンテナのログを取得する</h2><p><code>docker container logs</code>コマンドを使うと、BG実行したコンテナのログを端末に表示できます。<code>CONTAINER ID</code>または<code>docker container run</code>でコンテナを起動するときに<code>--name</code>オプションで指定した名前でコンテナを指定します。</p><pre><code>$ docker container logs -f db1(省略)2018-04-08T05:04:39.040525Z 0 [Note] Event Scheduler: Loaded 0 events2018-04-08T05:04:39.041069Z 0 [Note] mysqld: ready for connections.Version: &#39;5.7.21&#39;  socket: &#39;/var/run/mysqld/mysqld.sock&#39;  port: 3306  MySQL Community Server (GPL)</code></pre><p><code>-f(--follow)</code>オプションを指定すると、コンテナのSTDOUTとSTDERRから新しい出力があれば表示し続けます。</p><ul><li><a href="https://docs.docker.com/engine/reference/commandline/container_logs/" target="_blank" rel="noopener">docker container logs | Docker Documentation</a></li><li>旧コマンド名は <code>docker logs</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/logs/" target="_blank" rel="noopener">docker logs | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/logs.html" target="_blank" rel="noopener">docker logs | 日本語版 ドキュメント</a></li></ul></li></ul><h2 id="docker-container-rm-docker-rm-コンテナを削除する"><a href="#docker-container-rm-docker-rm-コンテナを削除する" class="headerlink" title="docker container rm (docker rm): コンテナを削除する"></a>docker container rm (docker rm): コンテナを削除する</h2><p><code>CONTAINER ID</code>または<code>docker container run</code>でコンテナを起動するときに<code>--name</code>オプションで指定した名前でコンテナを指定します。</p><pre><code># コンテナの停止$ docker container stop db1# コンテナの削除$ docker container rm db1</code></pre><p>または</p><pre><code># コンテナを停止して削除$ docker container rm -f db1</code></pre><ul><li><a href="https://docs.docker.com/engine/reference/commandline/container_stop/" target="_blank" rel="noopener">docker container stop | Docker Documentation</a></li><li>旧コマンド名は <code>docker stop</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/stop/" target="_blank" rel="noopener">docker stop | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/stop.html" target="_blank" rel="noopener">docker stop | 日本語版 ドキュメント</a></li></ul></li><li><a href="https://docs.docker.com/engine/reference/commandline/container_rm/" target="_blank" rel="noopener">docker container rm | Docker Documentation</a></li><li>旧コマンド名は <code>docker rm</code> です。ドキュメントはこちらの方が詳しいです。<ul><li><a href="https://docs.docker.com/engine/reference/commandline/rm/" target="_blank" rel="noopener">docker rm | Docker Documentation</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/rm.html" target="_blank" rel="noopener">docker rm | 日本語版 ドキュメント</a></li></ul></li></ul><h2 id="参考情報"><a href="#参考情報" class="headerlink" title="参考情報"></a>参考情報</h2><p>より詳細な使い方は下記のドキュメントを参照してください。</p><ul><li><a href="https://docs.docker.com/engine/reference/commandline/cli/" target="_blank" rel="noopener">Use the Docker command line | Docker Documentation</a></li></ul><p>また、対応しているバージョンが少し古いですが、日本語化されたドキュメントもあります。</p><ul><li><a href="http://docs.docker.jp/index.html" target="_blank" rel="noopener">Docker ドキュメント日本語化プロジェクト</a><ul><li><a href="http://docs.docker.jp/glossary.html" target="_blank" rel="noopener">用語集</a></li><li><a href="http://docs.docker.jp/engine/reference/commandline/toc.html" target="_blank" rel="noopener">dockerコマンドのリファレンス</a></li></ul></li></ul><p>次回は <a href="/blog/2018/05/30/docker-compose-with-rails/">Docker Compose/Dockerfile編</a> です。おすすめの書籍もこちらで紹介しています。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;RailsアプリケーションをKubernetes(k8s)で運用できるようにするための手順を書きます。&lt;/p&gt;
&lt;h1 id=&quot;概要&quot;&gt;&lt;a href=&quot;#概要&quot; class=&quot;headerlink&quot; title=&quot;概要&quot;&gt;&lt;/a&gt;概要&lt;/h1&gt;&lt;p&gt;この記事はシリーズ連載記事の第一回です。シリーズ全体の概要は次の通りです。&lt;/p&gt;
&lt;p&gt;はじめに、DockerやKubernetesの概要について簡単に説明しつつ、
&lt;code&gt;docker&lt;/code&gt;コマンドや&lt;code&gt;kubectl&lt;/code&gt;コマンドの基本的な使い方をチュートリアル風味で紹介します。&lt;/p&gt;
&lt;p&gt;次に、ごく小さな仕様の具体的なRailsアプリを題材として、
Docker Composeの構成ファイルやKubernetesのマニフェストの具体的なサンプルと使い方を示し、
最終的にHelmを使ってKubernetesクラスタにデプロイする方法を説明します。&lt;/p&gt;
&lt;p&gt;DockerとKubernetesの全機能を網羅的に学ぼうとすると膨大な時間が必要になるので、
この記事では「RailsアプリをKubernetesで運用すること」にフォーカスして一つ一つの概念や機能の詳細は必要最小限に絞り、
より詳細な資料へのポインタを置くに留めます。&lt;/p&gt;
&lt;p&gt;シリーズ全体の構成は次の通りです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一回 &lt;a href=&quot;/blog/2018/05/30/docker-with-rails/&quot;&gt;Docker編&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;最小限のDocker入門&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker&lt;/code&gt;コマンドのチュートリアル&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第二回 &lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;最小限のDocker Compose入門&lt;/li&gt;
&lt;li&gt;Docker Composeを使った各種ミドルウェアのインストールと管理&lt;/li&gt;
&lt;li&gt;RailsアプリケーションのDockerイメージの作り方&lt;/li&gt;
&lt;li&gt;Docker Composeによるローカルプレビュー環境の構築&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第三回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-tutorial/&quot;&gt;Kubernetes入門編&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;最小限のk8s入門&lt;/li&gt;
&lt;li&gt;minikubeの使い方&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt;コマンドのチュートリアル&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第四回 &lt;a href=&quot;/blog/2018/05/30/kubernetes-with-rails/&quot;&gt;Kubernetes基礎編&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt;コマンドとYAML形式のマニフェストファイルでRailsアプリをk8sにデプロイする方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第五回 &lt;a href=&quot;/blog/2018/05/30/practical-kubernetes-with-rails/&quot;&gt;Kubernetes応用編&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;基礎編で残した課題の解決編&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第六回 &lt;a href=&quot;/blog/2018/05/30/helm-with-rails/&quot;&gt;Helm編&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;最小限のHelm入門&lt;/li&gt;
&lt;li&gt;HelmでRailsアプリをk8sにデプロイする方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全てのサンプルコードはここにあります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kwhrtsk/rails-k8s-demoapp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kwhrtsk/rails-k8s-demoapp&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;前提&quot;&gt;&lt;a href=&quot;#前提&quot; class=&quot;headerlink&quot; title=&quot;前提&quot;&gt;&lt;/a&gt;前提&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;各種ツール類のインストール手順はmacOSでの作業を前提としています。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.brew.sh/Installation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Homebrew&lt;/a&gt;と&lt;a href=&quot;https://caskroom.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Homebrew-Cask&lt;/a&gt;を使います。&lt;ul&gt;
&lt;li&gt;Homebrew-Caskのインストールは&lt;code&gt;brew tap caskroom/cask&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Docker for Macを使います。&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://store.docker.com/editions/community/docker-ce-desktop-mac&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker Community Edition for Mac&lt;/a&gt;のGet Dockerをクリック&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;k8s関連ツールのインストール手順は後述します。&lt;/li&gt;
&lt;li&gt;ツールのインストール手順を除けば、Linuxでもおおよそ同じ手順で動作を確認できると思います。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;それぞれ下記のバージョンで動作を確認しています。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;バージョン&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;High Sierra(10.13.4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker for Mac&lt;/td&gt;
&lt;td&gt;18.0.3.1-ce-mac65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;2.5.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rails&lt;/td&gt;
&lt;td&gt;5.2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;minikube&lt;/td&gt;
&lt;td&gt;0.27.0 (k8s 1.10.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kubectl&lt;/td&gt;
&lt;td&gt;1.10.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;helm&lt;/td&gt;
&lt;td&gt;2.9.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hyperkit&lt;/td&gt;
&lt;td&gt;v0.20171204-60-g0e5b6b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;virtualbox&lt;/td&gt;
&lt;td&gt;5.2.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&quot;最小限のDocker入門&quot;&gt;&lt;a href=&quot;#最小限のDocker入門&quot; class=&quot;headerlink&quot; title=&quot;最小限のDocker入門&quot;&gt;&lt;/a&gt;最小限のDocker入門&lt;/h1&gt;&lt;p&gt;Docker初心者がRailsアプリをk8sで動かすまでに最低限必要なDockerについての知識と&lt;code&gt;docker&lt;/code&gt;コマンドの操作方法を書きます。
Rails固有のトピックを知りたい人は &lt;a href=&quot;/blog/2018/05/30/docker-compose-with-rails/&quot;&gt;Docker Compose/Dockerfile編&lt;/a&gt; までスキップしてください。&lt;/p&gt;
&lt;h2 id=&quot;Dockerとは&quot;&gt;&lt;a href=&quot;#Dockerとは&quot; class=&quot;headerlink&quot; title=&quot;Dockerとは&quot;&gt;&lt;/a&gt;Dockerとは&lt;/h2&gt;&lt;p&gt;LXC(Linux Containers)と呼ばれる技術を使ってアプリケーションの開発やデプロイを行うためのツール及びプラットフォームです。
LXCやDockerについて解説されたドキュメントは山ほどあるのでここでは詳細は割愛します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://knowledge.sakura.ad.jp/2108/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;15分で分かるLXC（Linux Containers）の仕組みと基本的な使い方 | さくらのナレッジ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.atmarkit.co.jp/ait/articles/1701/30/news037.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;第1回 Dockerとは : 超入門Docker - ＠IT&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;dockerのコマンド体系について&quot;&gt;&lt;a href=&quot;#dockerのコマンド体系について&quot; class=&quot;headerlink&quot; title=&quot;dockerのコマンド体系について&quot;&gt;&lt;/a&gt;dockerのコマンド体系について&lt;/h2&gt;&lt;p&gt;2017年1月にリリースされたDocker 1.13で &lt;code&gt;docker&lt;/code&gt; コマンドの構成が整理されました。
アナウンスは &lt;a href=&quot;https://blog.docker.com/2017/01/whats-new-in-docker-1-13/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Introducing Docker 1.13 - Docker Blog&lt;/a&gt;の&lt;code&gt;CLI restructured&lt;/code&gt;の部分です。&lt;/p&gt;
&lt;p&gt;「旧コマンドは引き続きサポートするが、新コマンドの使用を勧める」とのことなので
このドキュメントでは新しいコマンド体系で説明します。
ただ、古いドキュメントには旧コマンド体系で書かれているものも多いため、
対応する旧コマンドについても紹介します。&lt;/p&gt;
&lt;p&gt;このドキュメントで紹介するコマンドの新旧対応は下記の通りです。
基本的にはimageかcontainerのサブコマンドへの移動ですが、一部違うものがあるので太字にしています。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;旧コマンド&lt;/th&gt;
&lt;th&gt;新コマンド&lt;/th&gt;
&lt;th&gt;説明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;docker pull&lt;/td&gt;
&lt;td&gt;docker image pull&lt;/td&gt;
&lt;td&gt;イメージを取得する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker &lt;strong&gt;images&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;docker image &lt;strong&gt;ls&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;イメージの一覧を表示する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker rmi&lt;/td&gt;
&lt;td&gt;docker image &lt;strong&gt;rm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;イメージを削除する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker run&lt;/td&gt;
&lt;td&gt;docker container run&lt;/td&gt;
&lt;td&gt;コンテナを起動する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker &lt;strong&gt;ps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;docker container &lt;strong&gt;ls&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;コンテナの一覧を表示する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker exec&lt;/td&gt;
&lt;td&gt;docker container exec&lt;/td&gt;
&lt;td&gt;起動中のコンテナで新しいコマンドを実行する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker logs&lt;/td&gt;
&lt;td&gt;docker container logs&lt;/td&gt;
&lt;td&gt;コンテナのログを表示する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker rm&lt;/td&gt;
&lt;td&gt;docker container rm&lt;/td&gt;
&lt;td&gt;コンテナを削除する&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;以降の節で順にこれらのコマンドの使い方を説明します。見出しの括弧は旧コマンドです。&lt;/p&gt;
&lt;p&gt;また、各節の終わりにリファレンスへのリンクを置いていますが、
少なくとも本稿の執筆時点においては旧コマンドの方が詳細に書かれているので、必要に応じてそちらも参照してください。
なお日本語版のドキュメントにはまだ新コマンド版のリファレンスはありません。&lt;/p&gt;
&lt;h2 id=&quot;シェルの補完について&quot;&gt;&lt;a href=&quot;#シェルの補完について&quot; class=&quot;headerlink&quot; title=&quot;シェルの補完について&quot;&gt;&lt;/a&gt;シェルの補完について&lt;/h2&gt;&lt;p&gt;&lt;code&gt;docker&lt;/code&gt;のサブコマンドは、 &lt;code&gt;861162a44&lt;/code&gt; のようなハッシュ値や、
&lt;code&gt;romantic_neumann&lt;/code&gt; のようにランダムな英単語の組み合わせで自動生成されたコンテナ名をパラメータとして受け取ります。&lt;/p&gt;
&lt;p&gt;Docker for Macにはbashやzshでコマンドの補完を行うためのスクリプトが同梱されていますが、
ただインストールするだけでは有効になりません。
補完のための設定については別に記事を書いたのでこちらを参照してください。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chopschips.net/blog/2018/04/17/docker-completion/&quot;&gt;bash/zshとfzfでDocker関連コマンドの補完を行う方法&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;docker-image-pull-docker-pull-イメージを取得する&quot;&gt;&lt;a href=&quot;#docker-image-pull-docker-pull-イメージを取得する&quot; class=&quot;headerlink&quot; title=&quot;docker image pull (docker pull): イメージを取得する&quot;&gt;&lt;/a&gt;docker image pull (docker pull): イメージを取得する&lt;/h2&gt;&lt;p&gt;イメージとはコンテナの雛形です。&lt;code&gt;docker image pull&lt;/code&gt;コマンドでリポジトリとタグを指定し、イメージを取得します。
MySQLの公式リポジトリから&lt;code&gt;5.7.21&lt;/code&gt;のタグが付いたイメージを取得する場合は下記のようにします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ 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
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;タグは、多くの場合バージョンを指します。ただし、ここでいうバージョンはイメージのバージョンではなく、
アプリケーションのバージョンであることが多いため、
同じタグを指定してもタイミングによって違うイメージを取得するケースがある点に注意が必要です。&lt;/p&gt;
&lt;p&gt;例えば&lt;code&gt;mysql:5.7&lt;/code&gt;は現時点では&lt;code&gt;mysql:5.7.21&lt;/code&gt;と同じイメージを指していますが、以前は&lt;code&gt;mysql:5.7.20&lt;/code&gt;を指していました。
タグを指定しないと&lt;code&gt;latest&lt;/code&gt;というタグを指定したとみなされます。&lt;/p&gt;
&lt;p&gt;リポジトリを管理しているサービスをレジストリと呼びます。
MySQLやRedisなどの主要なプロダクトの公式イメージは Docker Hub というレジストリで配布されており、
上記の例ではDocker Hub上のリポジトリからイメージを取得しています。
Docker Hub上のリポジトリは下記のページで探すことができます。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hub.docker.com/explore/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hub.docker.com/explore/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Docker Hub以外のレジストリを使う場合は下記のようにリポジトリの前にレジストリのホスト名とポート番号を書きます。
例えばローカルレジストリ &lt;code&gt;myregistry.local:5000&lt;/code&gt; から &lt;code&gt;testing/test-image&lt;/code&gt; を取得する場合は下記のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker image pull myregistry.local:5000/testing/test-image
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/engine/reference/commandline/image_pull/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docker image pull | Docker Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;旧コマンド名は &lt;code&gt;docker pull&lt;/code&gt; です。ドキュメントはこちらの方が詳しいです。&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/engine/reference/commandline/pull/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docker pull | Docker Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://docs.docker.jp/engine/reference/commandline/pull.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docker pull | 日本語版 ドキュメント&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
      <category term="HowTo" scheme="https://chopschips.net/categories/HowTo/"/>
    
    
      <category term="rails" scheme="https://chopschips.net/tags/rails/"/>
    
      <category term="docker" scheme="https://chopschips.net/tags/docker/"/>
    
      <category term="kubernetes" scheme="https://chopschips.net/tags/kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>bash/zshとfzfでDocker関連コマンドの補完を行う方法</title>
    <link href="https://chopschips.net/blog/2018/04/17/docker-completion/"/>
    <id>https://chopschips.net/blog/2018/04/17/docker-completion/</id>
    <published>2018-04-17T13:45:51.000Z</published>
    <updated>2018-04-19T14:52:02.110Z</updated>
    
    <content type="html"><![CDATA[<p>dockerコマンドはパラメータにコンテナIDやイメージIDを取るケースがあって入力が面倒です。公式のbash/zsh用補完スクリプトを使うと各種ID類も補完できるようになるのですが、Docker for Macをインストールするだけではそれらのスクリプトは有効にならないので、使い方を解説します。</p><p>また、fzfを使って候補のフィルタリングや複数選択を楽に行えるようにする方法についても説明します。</p><p>最終的にはこんな感じになります。</p><img src="/images/post_20180417_docker_completion/docker-fzf-completion.gif"><h1 id="dockerとdocker-composeの補完スクリプト"><a href="#dockerとdocker-composeの補完スクリプト" class="headerlink" title="dockerとdocker-composeの補完スクリプト"></a>dockerとdocker-composeの補完スクリプト</h1><p>Docker for Macにはbashとzsh用の補完スクリプトが同梱されています。</p><pre><code>% ls -1 /Applications/Docker.app/Contents/Resources/etc/docker-compose.bash-completiondocker-compose.zsh-completiondocker-machine.bash-completiondocker-machine.zsh-completiondocker.bash-completiondocker.fish-completiondocker.zsh-completion</code></pre><p>適切に設定すれば下記のようにサブコマンドやイメージIDなどを補完できます。</p><img src="/images/post_20180417_docker_completion/docker-completion.gif"><a id="more"></a><h2 id="zshの場合"><a href="#zshの場合" class="headerlink" title="zshの場合"></a>zshの場合</h2><p>下記のように補完スクリプトのリンクを作って <code>~/.zshrc</code> で読み込むようにすると、<code>docker</code>コマンドと<code>docker-compose</code>コマンドに補完が効くようになります。</p><pre><code>$ mkdir ~/.zsh/completions$ ln -s /Applications/Docker.app/Contents/Resources/etc/docker.zsh-completion ~/.zsh/completions/_docker$ ln -s /Applications/Docker.app/Contents/Resources/etc/docker-compose.zsh-completion ~/.zsh/completions/_docker-compose</code></pre><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># ~/.zshrc</span><span class="token keyword">if</span> <span class="token punctuation">[</span> -e ~/.zsh/completions <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>  fpath<span class="token operator">=</span><span class="token punctuation">(</span>~/.zsh/completions <span class="token variable">$fpath</span><span class="token punctuation">)</span><span class="token keyword">fi</span>autoload -U compinitcompinit<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="bashの場合"><a href="#bashの場合" class="headerlink" title="bashの場合"></a>bashの場合</h2><p><code>bash-completion</code>が必要です。Homebrewでインストールできます。</p><pre><code>$ brew install bash-completion</code></pre><p>下記のように補完スクリプトのリンクを作って <code>~/.bash_profile</code> で読み込むようにすると、<code>docker</code>コマンドと<code>docker-compose</code>コマンドに補完が効くようになります。</p><pre><code>$ ln -s /Applications/Docker.app/Contents/Resources/etc/docker-compose.bash-completion /usr/local/etc/bash_completion.d/$ ln -s /Applications/Docker.app/Contents/Resources/etc/docker.bash-completion /usr/local/etc/bash_completion.d/</code></pre><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># ~/.bash_profile</span><span class="token keyword">if</span> <span class="token punctuation">[</span> -f <span class="token variable"><span class="token variable">$(</span>brew --prefix<span class="token variable">)</span></span>/etc/bash_completion <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>  <span class="token keyword">.</span> <span class="token variable"><span class="token variable">$(</span>brew --prefix<span class="token variable">)</span></span>/etc/bash_completion<span class="token keyword">fi</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="fzfによる補完"><a href="#fzfによる補完" class="headerlink" title="fzfによる補完"></a>fzfによる補完</h1><p>fzfと組み合わせると、<code>docker rm</code>や<code>docker rmi</code>のようなコマンドで、パラメータのコンテナやイメージをフィルタしたり複数選択したりできます。</p><h2 id="fzfのインストール"><a href="#fzfのインストール" class="headerlink" title="fzfのインストール"></a>fzfのインストール</h2><p>Homebrewでインストールできます。</p><pre><code>$ brew install fzf</code></pre><p>fzfのインストールスクリプトを実行します。</p><pre><code>$ /usr/local/opt/fzf/install</code></pre><p>質問に答えると <code>~/.fzf.zsh</code>と<code>~/.fzf.bash</code>が作成されます。<code>~/.zshrc</code>に下記を追加してこれを有効にします。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># ~/.zshrc</span><span class="token punctuation">[</span> -f ~/.fzf.zsh <span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token function">source</span> ~/.fzf.zsh<span class="token function">export</span> FZF_COMPLETION_TRIGGER<span class="token operator">=</span><span class="token string">","</span> <span class="token comment" spellcheck="true"># default: '**'</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>FZF_COMPLETION_TRIGGERは、fzfによる補完を開始するトリガーです。ここで指定した文字列の後にタブを入力するとfzfによる補完が始まります。デフォルトでは <code>**</code> ですが、zshのグロブ補完と重複していて気持ち悪いので、上記の例では<code>,</code>に変更しています。この記事のスクリーンキャストでもこの設定を有効にしていて、<code>docker rmi ,</code>まで入力してからタブを押して補完を開始しています。</p><h2 id="docker-fzf-completionのインストール"><a href="#docker-fzf-completionのインストール" class="headerlink" title="docker-fzf-completionのインストール"></a>docker-fzf-completionのインストール</h2><p>下記のリポジトリから <code>docker-fzf.zsh</code> を取得して、<code>~/.zshrc</code>で読み込みます。</p><p><a href="https://github.com/kwhrtsk/docker-fzf-completion" target="_blank" rel="noopener">https://github.com/kwhrtsk/docker-fzf-completion</a></p><p>git cloneでもコピペでもなんでもいいので、ローカルマシンのどこかに置いて<code>source</code>で読み込んでください。bashの場合も <code>docker-fzf.bash</code> を取得して <code>~/.bash_profile</code> で <code>source</code> すれば使えると思います。</p><p>自分の場合はzshで下記のようにしています。</p><pre><code>$ ghq get https://github.com/kwhrtsk/docker-fzf-completion# これと同じ# mkdir -p mkdir ~/.ghq/github.com/kwhrtsk/# cd ~/.ghq/github.com/kwhrtsk/# git clone https://github.com/kwhrtsk/docker-fzf-completion</code></pre><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># ~/.zshrc</span><span class="token function">source</span> ~/.ghq/github.com/kwhrtsk/docker-fzf-completion/docker-fzf.zsh<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>上記のスクリプトは下記のリポジトリをフォークして作った二次著作物です。(MITライセンス)</p><p><a href="https://github.com/Mike-Now/docker-fzf-completion" target="_blank" rel="noopener">https://github.com/Mike-Now/docker-fzf-completion</a></p><p>オリジナルに対して下記の修正を行っています。</p><ul><li>補完対象のコマンドを追加<ul><li><code>attach</code>, <code>kill</code>, <code>logs</code>, <code>stats</code>, <code>history</code>などコンテナとイメージに関するものほぼ全部</li><li>Docker 1.13で追加された<code>container</code>と<code>image</code>のサブコマンド群ほぼ全部</li></ul></li><li>コンテナの状態を考慮した補完<ul><li><code>start</code>には停止中のコンテナしか補完しないetc</li></ul></li><li>イメージに対する操作で<code>リポジトリ名:タグ</code>が空(<code>&lt;none&gt;:&lt;none&gt;</code>)の場合は代わりに<code>IMAGE_ID</code>を使うように修正</li></ul><h2 id="docker-fzf-completionの使い方"><a href="#docker-fzf-completionの使い方" class="headerlink" title="docker-fzf-completionの使い方"></a>docker-fzf-completionの使い方</h2><p><code>docker run</code>や<code>docker rm</code>のようなコマンドの後、<code>FZF_COMPLETION_TRIGGER</code>で指定した文字列(下記の例では<code>,</code>)を入力してタブを押すと補完が始まります。</p><img src="/images/post_20180417_docker_completion/docker-fzf-completion.gif"><ul><li>プロンプトに文字列を入力するとフィルタされます。</li><li>矢印キーまたは<code>Ctrl+P</code>、<code>Ctrl+N</code>でカーソルを上下に移動します。</li><li><code>Enter</code>を押すとカーソル位置のコンテナ名またはイメージIDがコマンドのパラメータとして入力されます。</li><li>補完中にタブを押すとカーソル位置のアイテムを複数選択できます。</li><li><code>Ctrl+A</code>で全選択、<code>Ctrl+D</code>で全選択解除、<code>Ctrl+T</code>で全選択をトグルします。</li></ul><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://docs.docker.com/compose/completion/" target="_blank" rel="noopener">Command-line completion | Docker Documentation</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;dockerコマンドはパラメータにコンテナIDやイメージIDを取るケースがあって入力が面倒です。
公式のbash/zsh用補完スクリプトを使うと各種ID類も補完できるようになるのですが、
Docker for Macをインストールするだけではそれらのスクリプトは有効にならないので、使い方を解説します。&lt;/p&gt;
&lt;p&gt;また、fzfを使って候補のフィルタリングや複数選択を楽に行えるようにする方法についても説明します。&lt;/p&gt;
&lt;p&gt;最終的にはこんな感じになります。&lt;/p&gt;
&lt;img src=&quot;/images/post_20180417_docker_completion/docker-fzf-completion.gif&quot;&gt;
&lt;h1 id=&quot;dockerとdocker-composeの補完スクリプト&quot;&gt;&lt;a href=&quot;#dockerとdocker-composeの補完スクリプト&quot; class=&quot;headerlink&quot; title=&quot;dockerとdocker-composeの補完スクリプト&quot;&gt;&lt;/a&gt;dockerとdocker-composeの補完スクリプト&lt;/h1&gt;&lt;p&gt;Docker for Macにはbashとzsh用の補完スクリプトが同梱されています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;% 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
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;適切に設定すれば下記のようにサブコマンドやイメージIDなどを補完できます。&lt;/p&gt;
&lt;img src=&quot;/images/post_20180417_docker_completion/docker-completion.gif&quot;&gt;
    
    </summary>
    
      <category term="HowTo" scheme="https://chopschips.net/categories/HowTo/"/>
    
    
      <category term="docker" scheme="https://chopschips.net/tags/docker/"/>
    
      <category term="bash" scheme="https://chopschips.net/tags/bash/"/>
    
      <category term="zsh" scheme="https://chopschips.net/tags/zsh/"/>
    
  </entry>
  
  <entry>
    <title>Rails 5.2.0 で問題の起きるgem(simple_form, annotate)</title>
    <link href="https://chopschips.net/blog/2018/04/11/rails-5-2-0/"/>
    <id>https://chopschips.net/blog/2018/04/11/rails-5-2-0/</id>
    <published>2018-04-11T13:23:47.000Z</published>
    <updated>2018-04-12T15:26:13.000Z</updated>
    
    <content type="html"><![CDATA[<p>Rails 5.2.0が正式にリリースされましたね。めでたい。</p><ul><li><a href="http://weblog.rubyonrails.org/2018/4/9/Rails-5-2-0-final/" target="_blank" rel="noopener">Rails 5.2.0 FINAL: Active Storage, Redis Cache Store, HTTP/2 Early Hints, CSP, Credentials | Riding Rails</a></li><li><a href="https://techracho.bpsinc.jp/hachi8833/2018_04_10/55079" target="_blank" rel="noopener">【速報】Rails 5.2.0正式リリース！Active Storage、Redis Cache Store、HTTP/2 Early Hintほか</a></li></ul><p>いつものことですが、2018-04-11現在、Rails 5.2.0だと正常に動作しないgemがあります。とりあえず見つけたものをメモしておきます。</p><h1 id="simple-form"><a href="#simple-form" class="headerlink" title="simple_form"></a>simple_form</h1><p><a href="https://github.com/plataformatec/simple_form/issues/1568" target="_blank" rel="noopener">Rails 5.2.0 upgrade causes “Undefined method wrapper for SimpleForm:Module” error  Issue #1568 · plataformatec/simple_form</a></p><p>Gemfileでsimple_formのバージョンを指定していない場合、railsのバージョンを5.2.0に上げて<code>bundle update</code>したらsimple_formのバージョンが3.5.1から1.4.1まで下がるという現象が起きていました。上記はそのために引き起こされるエラーです。</p><p>単にgemspecの要求バージョンが厳しかったみたいで、このブログを書いている間に 4.0.0 がリリースされて解決しました。</p><p><a href="https://github.com/plataformatec/simple_form/blob/master/CHANGELOG.md#400" target="_blank" rel="noopener">https://github.com/plataformatec/simple_form/blob/master/CHANGELOG.md#400</a></p><p>3.5系でバージョンをロックしている人は4.0.0を試してみましょう。</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token comment" spellcheck="true"># Gemfile</span><span class="token comment" spellcheck="true"># gem "simple_form", "~> 3.5.1"</span>gem <span class="token string">"simple_form"</span><span class="token punctuation">,</span> <span class="token string">"~> 4.0.0"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h1 id="annotate"><a href="#annotate" class="headerlink" title="annotate"></a>annotate</h1><p><a href="https://github.com/ctran/annotate_models/issues/538" target="_blank" rel="noopener">Annotate no longer works with Rails 5.2 models  Issue #538 · ctran/annotate_models</a></p><p><code>./bin/rails annotate_models</code> または <code>./bin/rails db:migrate</code> など、モデルのアノテーションを更新する際に下記のようなエラーが起きる場合があります。</p><pre class="line-numbers language-console"><code class="language-console">Unable to annotate app/models/author.rb: can't modify frozen StringUnable to annotate app/models/author.rb: no implicit conversion of nil into Array<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>暫定的な措置として、設定でインデックスに関する情報の記入を無効化すればエラーを回避できます。</p><pre class="line-numbers language-diff"><code class="language-diff">diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rakeindex 9088128..9c2f5d5 100644<span class="token coord">--- a/lib/tasks/auto_annotate_models.rake</span><span class="token coord">+++ b/lib/tasks/auto_annotate_models.rake</span>@@ -16,7 +16,7 @@ if Rails.env.development?       'position_in_serializer'    => 'before',       'show_foreign_keys'         => 'true',       'show_complete_foreign_keys' => 'false',<span class="token deleted">-      'show_indexes'              => 'true',</span><span class="token inserted">+      'show_indexes'              => 'false',</span>       'simple_indexes'            => 'false',       'model_dir'                 => 'app/models',       'root_dir'                  => '',<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>他にも何か見つけたら更新していきます。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Rails 5.2.0が正式にリリースされましたね。めでたい。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://weblog.rubyonrails.org/2018/4/9/Rails-5-2-0-final/&quot; target=&quot;_blank&quot; rel=&quot;noo
      
    
    </summary>
    
      <category term="Development" scheme="https://chopschips.net/categories/Development/"/>
    
    
      <category term="rails" scheme="https://chopschips.net/tags/rails/"/>
    
  </entry>
  
  <entry>
    <title>chef社のVagrant Boxがbento organizationに移行してた</title>
    <link href="https://chopschips.net/blog/2015/08/28/chef-vagrant-box-migrate-to-bento/"/>
    <id>https://chopschips.net/blog/2015/08/28/chef-vagrant-box-migrate-to-bento/</id>
    <published>2015-08-28T11:13:06.000Z</published>
    <updated>2015-08-28T11:30:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>Vagrantを使うときに、どこの馬の骨ともしれないboxは使いたくないので chef 社が公式にサポートしてるboxを使っていました。</p><pre><code># VagrantfileVagrant.configure(2) do |config|  # こんなの  config.vm.box = &quot;chef/centos-7.1&quot;end</code></pre><p>8/27か28あたりに配布されなくなったようです。</p><p><a href="https://vagrantcloud.com/chef" target="_blank" rel="noopener">https://vagrantcloud.com/chef</a></p><p>跡地でBentoというorganizationへ誘導しています。</p><p><a href="https://atlas.hashicorp.com/bento/" target="_blank" rel="noopener">https://atlas.hashicorp.com/bento/</a></p><p>BentoというのもChef社にメンテナンスされているプロジェクトで、PackerテンプレートでVagrantのbase boxイメージを作ることができます。</p><p>彼ら自身がchefをテストするためのboxイメージを作るためにも使っているとのこと。</p><p><a href="https://github.com/chef/bento" target="_blank" rel="noopener">https://github.com/chef/bento</a></p><p>今後はこんな風にBento boxを使うことにします。</p><pre><code># VagrantfileVagrant.configure(2) do |config|  config.vm.box = &quot;bento/centos-7.1&quot;end</code></pre><p>(おそらく公式の)BentoのTwitterアイコンがすごく弁当箱です。集中線がジワジワくる。</p><p><a href="https://twitter.com/bento_chef" target="_blank" rel="noopener">https://twitter.com/bento_chef</a></p><p><img src="https://pbs.twimg.com/profile_images/618930596438372352/rWntD1GI.png"></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Vagrantを使うときに、どこの馬の骨ともしれないboxは使いたくないので chef 社が公式にサポートしてるboxを使っていました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Vagrantfile
Vagrant.configure(2) do |config|
  # こん
      
    
    </summary>
    
      <category term="開発環境" scheme="https://chopschips.net/categories/%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83/"/>
    
    
      <category term="chef" scheme="https://chopschips.net/tags/chef/"/>
    
      <category term="vagrant" scheme="https://chopschips.net/tags/vagrant/"/>
    
      <category term="bento" scheme="https://chopschips.net/tags/bento/"/>
    
  </entry>
  
  <entry>
    <title>Vagrantでknife-zeroを使うためのベストプラクティス</title>
    <link href="https://chopschips.net/blog/2015/08/25/vagrant-knife-zero-best-practice/"/>
    <id>https://chopschips.net/blog/2015/08/25/vagrant-knife-zero-best-practice/</id>
    <published>2015-08-25T14:15:00.000Z</published>
    <updated>2015-08-28T10:58:47.000Z</updated>
    
    <content type="html"><![CDATA[<p>Vagrantでknife-zeroを試そうとして挫折する人が多いらしいので、自分の考えるベストプラクティスを書いてみます。</p><p>追記：記事の一部を更新しました。詳細は末尾の更新履歴でご確認ください。</p><p>以下、Vagrantを開発環境で利用することを想定しています。</p><p>下記のドキュメントを参考にしました。</p><ul><li><a href="http://qiita.com/sawanoboly/items/ae3c96734c5cee72863c" target="_blank" rel="noopener">Knife-ZeroとVagrant(単品VM) - Qiita</a></li></ul><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h2><p>プライベートネットワークモードで立ち上げた2つのVMに、knife-zeroでレシピを適用する具体的な手順を説明します。</p><p>基本方針は下記の通り。</p><ul><li>コマンドラインでのパラメータを極力減らす。</li><li>IPアドレスの記述は.envrcに集約して、その他の設定ファイルやコマンドラインでは環境変数を使う。</li></ul><p>適切に設定ファイルを書けば、下記のような一連のコマンドでレシピの適用まで実行することができます。</p><pre class="line-numbers language-bash"><code class="language-bash">% <span class="token function">cd</span> knife_zero_example% direnv allow% vagrant up% bundle <span class="token function">install</span> --path<span class="token operator">=</span>vendor/bundle --binstubs% ./bin/berks vendor cookbooks% ./bin/knife zero bootstrap <span class="token variable">$VAGRANT_HOST001</span>% ./bin/knife zero bootstrap <span class="token variable">$VAGRANT_HOST002</span>% ./bin/knife node run_list <span class="token keyword">set</span> host001.example build-essential% ./bin/knife node run_list <span class="token keyword">set</span> host002.example build-essential% ./bin/knife zero converge <span class="token string">'name:*.example'</span> -a knife_zero.host<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><a id="more"></a><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><h3 id="動作確認環境"><a href="#動作確認環境" class="headerlink" title="動作確認環境"></a>動作確認環境</h3><ul><li>Yosemite 10.10.5</li><li>Vagrant 1.7.4</li><li>VirtualBox 5.0.0</li><li>ruby 2.2.3</li><li>chef 12.4.1</li><li>knife-zero 1.8.0</li><li>berkshelf 3.3.0</li><li>direnv 2.6.0</li><li>CentOS 7.1(VM on VirtualBox)</li></ul><h3 id="目標"><a href="#目標" class="headerlink" title="目標"></a>目標</h3><ul><li>vagrantで2台のVM(CentOS 7.1)をセットアップ</li><li>プライベートネットワークで互いに疎通</li><li>2台のVMにコミュニティクックブックの <a href="https://supermarket.chef.io/cookbooks/build-essential" target="_blank" rel="noopener">build-essential</a> を適用</li></ul><p>VMのホスト名とIPアドレスは下記とします。</p><table><thead><tr><th>hostname</th><th>ip address</th></tr></thead><tbody><tr><td>host001.example</td><td>192.168.33.10</td></tr><tr><td>host002.example</td><td>192.168.33.11</td></tr></tbody></table><p>ネットワークアドレスは、<code>VBoxManage list hostonlyifs</code> コマンドでVirtualBoxのホストオンリーネットワークのアドレスを確認して、そのレンジのアドレスを設定するようにしてください。普通にVirtualBoxをインストールすると下記のような出力になると思います。</p><pre class="line-numbers language-bash"><code class="language-bash">% VBoxManage list hostonlyifsName:            vboxnet0GUID:            786f6276-656e-4074-8000-0a0027000000DHCP:            DisabledIPAddress:       192.168.33.1NetworkMask:     255.255.255.0IPV6Address:IPV6NetworkMaskPrefixLength: 0HardwareAddress: 0a:00:27:00:00:00MediumType:      EthernetStatus:          UpVBoxNetworkName: HostInterfaceNetworking-vboxnet0<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>この場合、<code>192.168.33.0/24</code>のレンジでアドレスを設定する必要があります。</p><h2 id="手順"><a href="#手順" class="headerlink" title="手順"></a>手順</h2><h3 id="VMのアドレスを環境変数に設定-envrc"><a href="#VMのアドレスを環境変数に設定-envrc" class="headerlink" title="VMのアドレスを環境変数に設定(.envrc)"></a>VMのアドレスを環境変数に設定(.envrc)</h3><p>VMのIPアドレスを環境変数に設定します。これらの環境変数はdirenvで自動的にロード・アンロードするようにします。</p><pre class="line-numbers language-bash"><code class="language-bash">% <span class="token function">mkdir</span> knife_zero_example % <span class="token function">cd</span> knife_zero_example% <span class="token function">cat</span> <span class="token operator">&lt;&lt;</span>EOF <span class="token operator">></span> .envrc<span class="token function">export</span> VAGRANT_HOST001<span class="token operator">=</span>192.168.33.10<span class="token function">export</span> VAGRANT_HOST002<span class="token operator">=</span>192.168.33.11EOF% direnv allow<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>direnvがない場合は直接読み込んでもよいです。</p><pre class="line-numbers language-bash"><code class="language-bash">% <span class="token function">source</span> ~/.envrc<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="Vagrantfileの作成"><a href="#Vagrantfileの作成" class="headerlink" title="Vagrantfileの作成"></a>Vagrantfileの作成</h3><pre class="line-numbers language-bash"><code class="language-bash">% <span class="token function">cat</span> <span class="token operator">&lt;&lt;</span>EOF<span class="token operator">></span> VagrantfileVagrant.configure<span class="token punctuation">(</span>2<span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token operator">|</span>config<span class="token operator">|</span>  config.vm.box <span class="token operator">=</span> <span class="token string">"bento/centos-7.1"</span>  config.vm.define :host001 <span class="token keyword">do</span> <span class="token operator">|</span>host<span class="token operator">|</span>    host.vm.hostname <span class="token operator">=</span> <span class="token string">"host001.example"</span>    host.vm.network <span class="token string">"private_network"</span>, ip: ENV<span class="token punctuation">[</span><span class="token string">'VAGRANT_HOST001'</span><span class="token punctuation">]</span>  end  config.vm.define :host002 <span class="token keyword">do</span> <span class="token operator">|</span>host<span class="token operator">|</span>    host.vm.hostname <span class="token operator">=</span> <span class="token string">"host002.example"</span>    host.vm.network <span class="token string">"private_network"</span>, ip: ENV<span class="token punctuation">[</span><span class="token string">'VAGRANT_HOST002'</span><span class="token punctuation">]</span>  end  <span class="token comment" spellcheck="true"># VMにログインする鍵を固定</span>  config.ssh.insert_key <span class="token operator">=</span> <span class="token boolean">false</span>endEOF<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="ssh-config-の設定変更"><a href="#ssh-config-の設定変更" class="headerlink" title="~/.ssh/config の設定変更"></a>~/.ssh/config の設定変更</h3><p><code>~/.ssh/config</code>に下記のエントリを追加します。</p><pre class="line-numbers language-bash"><code class="language-bash">% <span class="token function">cat</span> <span class="token operator">&lt;&lt;</span>EOF<span class="token operator">>></span> ~/.ssh/config<span class="token comment" spellcheck="true"># Vagrant private network ip</span>Host 192.168.33.*  UserKnownHostsFile /dev/null  StrictHostKeyChecking no  LogLevel FATAL  User vagrant  IdentityFile ~/.vagrant.d/insecure_private_keyEOF<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>vagrantで使うプライベートネットワークでのデフォルトのログインユーザをvagrantにし、鍵をデフォルトのものに固定します。</p><p>IdentityFileは、もしもVAGRANT_HOME環境変数を設定している場合には、その下のinsecure_private_keyファイルのパスに変更してください。</p><h3 id="VMの立ち上げと疎通確認"><a href="#VMの立ち上げと疎通確認" class="headerlink" title="VMの立ち上げと疎通確認"></a>VMの立ち上げと疎通確認</h3><p>VMを立ち上げて、ログインできるか確認して下さい。</p><pre class="line-numbers language-bash"><code class="language-bash">% vagrant up% <span class="token function">ssh</span> <span class="token variable">$VAGRANT_HOST001</span> <span class="token function">hostname</span>host001.example% <span class="token function">ssh</span> <span class="token variable">$VAGRANT_HOST002</span> <span class="token function">hostname</span>host002.example<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="knife-zeroとberkshelfのインストール"><a href="#knife-zeroとberkshelfのインストール" class="headerlink" title="knife-zeroとberkshelfのインストール"></a>knife-zeroとberkshelfのインストール</h3><p>knife-zeroとberkshelfをインストールします。</p><pre class="line-numbers language-bash"><code class="language-bash">% <span class="token function">cat</span> <span class="token operator">&lt;&lt;</span>EOF<span class="token operator">></span> Gemfile<span class="token function">source</span> <span class="token string">"https://rubygems.org"</span>gem <span class="token string">"knife-zero"</span>gem <span class="token string">"berkshelf"</span>EOF% bundle <span class="token function">install</span> --path<span class="token operator">=</span>vendor/bundle --binstubs<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>knifeコマンドのオプション引数を省略するために、<code>.chef/knife.rb</code>を作成します。</p><pre class="line-numbers language-bash"><code class="language-bash">% <span class="token function">mkdir</span> -p .chef% <span class="token function">cat</span> <span class="token operator">&lt;&lt;</span>EOF<span class="token operator">></span> .chef/knife.rb<span class="token comment" spellcheck="true"># -z, --local-mode</span>local_mode <span class="token boolean">true</span><span class="token comment" spellcheck="true"># -x vagrant, --ssh-user vagrant</span>knife<span class="token punctuation">[</span>:ssh_user<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"vagrant"</span><span class="token comment" spellcheck="true"># --sudo</span>knife<span class="token punctuation">[</span>:use_sudo<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span>EOF<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>knife.rb</code> のその他の設定は下記のドキュメントを参照してください。</p><p><a href="https://docs.chef.io/config_rb_knife_optional_settings.html" target="_blank" rel="noopener">knife.rb Optional Settings — Chef Docs</a></p><p>今回はknife-zeroの利用例として、コミュニティクックブックのbuild-essentialを2つのVMにインストールします。</p><p>このコミュニティクックブックを使うための設定をします。</p><pre class="line-numbers language-bash"><code class="language-bash">% <span class="token function">cat</span> <span class="token operator">&lt;&lt;</span>EOF<span class="token operator">></span> Berksfile<span class="token function">source</span> <span class="token string">"https://supermarket.chef.io"</span>cookbook <span class="token string">"build-essential"</span>EOF% ./bin/berks vendor cookbooks<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="ノードの初期設定"><a href="#ノードの初期設定" class="headerlink" title="ノードの初期設定"></a>ノードの初期設定</h3><p>knife-zeroを使って、VMにchefをインストールするとともに、ノードとしてchef-zeroサーバに登録します。</p><pre class="line-numbers language-bash"><code class="language-bash">% ./bin/knife zero bootstrap <span class="token variable">$VAGRANT_HOST001</span>% ./bin/knife zero bootstrap <span class="token variable">$VAGRANT_HOST002</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><code>nodes/</code>ディレクトリと<code>clients/</code>ディレクトリ以下にそれぞれjsonファイルができていることを確認して下さい。</p><p>また、<code>knife node list</code>コマンドを実行して、登録したVMのホスト名が表示されることを確認して下さい。</p><pre class="line-numbers language-bash"><code class="language-bash">% ./bin/knife node listhost001.examplehost002.example<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="レシピの適用"><a href="#レシピの適用" class="headerlink" title="レシピの適用"></a>レシピの適用</h3><p>各ノードのrun_listにbuild-essentialレシピを追加します。</p><pre class="line-numbers language-bash"><code class="language-bash">% ./bin/knife node run_list <span class="token keyword">set</span> host001.example build-essentialhost001.example:  run_list: recipe<span class="token punctuation">[</span>build-essential<span class="token punctuation">]</span>% ./bin/knife node run_list <span class="token keyword">set</span> host002.example build-essentialhost002.example:  run_list: recipe<span class="token punctuation">[</span>build-essential<span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>次に各ノードへレシピを適用します。</p><p>knife-soloではノードにレシピを適用するとき、<code>knife solo cook FQDN</code>でホストを一つ指定します。<br>knife-zeroでは、<code>knife solo cook FQDN</code>に相当するコマンドは<code>knife zero converge QUERY</code>です。<br>QUERYで指定した条件にマッチするすべてのホストに対して並列にsshログインし、<code>chef-client</code>を実行してレシピを適用します。</p><p><code>knife zero converge QUERY</code>では、対象のノードにsshでログインするためのIPアドレスをノード情報のAttribute名で指定する必要があります(<code>-a</code>オプション)。このAttributeは通常<code>ipaddress</code>で良く、その場合は省略できます。ただし、Vagrant + VirtualBoxのprivate_networkでIPアドレスを固定したVMに<code>knife zero bootstrap</code>した場合、ノードのAttributeとして記録される<code>ipaddress</code>は、Vagrantfileで指定したアドレス<code>192.168.33.*</code>ではなく、<code>10.0.2.*</code>のように異なるネットワークのアドレスになっているはずです。(さらにおそらくアドレス自体が同じ)</p><pre class="line-numbers language-bash"><code class="language-bash">% ./bin/knife search node -a ipaddress2 items foundhost001.example:  ipaddress: 10.0.2.15host002.example:  ipaddress: 10.0.2.15<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>knife-zeroのバージョン1.8.0以降であれば、bootstrap時に<code>knife_zero.host</code>というAttributeに接続時のアドレスが記録されます。</p><pre><code>% ./bin/knife search node -a knife_zero.host2 items foundhost001.example:  knife_zero.host: 192.168.33.10host002.example:  knife_zero.host: 192.168.33.11</code></pre><p>なので下記のように<code>knife_zero.host</code>を指定すればレシピを適用できます。</p><pre class="line-numbers language-bash"><code class="language-bash">% ./bin/knife zero converge <span class="token string">'name:*'</span> -a knife_zero.host<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><code>&#39;name:*&#39;</code>はクエリ文字列で、この例ではすべてのノードを対象にしています。</p><p>クエリの構造は <code>knife search</code> と同様です。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># host001だけを指定</span>% ./bin/knife search node <span class="token string">'name:host001.example'</span><span class="token comment" spellcheck="true"># exampleドメインに一致するホストだけを指定</span>% ./bin/knife search node <span class="token string">'name:*.example'</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>また、クエリ指定でsshログインして任意のコマンドを実行することもできます。</p><pre class="line-numbers language-bash"><code class="language-bash">% ./bin/knife <span class="token function">ssh</span> <span class="token string">'name:*'</span> -a knife_zero.host <span class="token string">'sudo yum update -y'</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="補足"><a href="#補足" class="headerlink" title="補足"></a>補足</h2><h3 id="ノード情報の保存先"><a href="#ノード情報の保存先" class="headerlink" title="ノード情報の保存先"></a>ノード情報の保存先</h3><p><code>knife exec</code>や<code>knife node edit</code>で変更した情報は <code>nodes/*.json</code> に保存されています。</p><p>これは本質的にはknife-solo(chef-solo)で書くものと同じなのですが、knife-zero(chef-zero)では非常に分量が多いです。</p><p>これについては、<code>.chef/knife.rb</code>にAttributeのホワイトリストを書くことで制御できます。</p><p><a href="http://qiita.com/sawanoboly/items/28dfc22929b8fa961456" target="_blank" rel="noopener">chef-zero - Knife-Zeroで管理するnodeオブジェクトを任意のattributesに限定する - Qiita</a></p><h3 id="Attributeのタイプ"><a href="#Attributeのタイプ" class="headerlink" title="Attributeのタイプ"></a>Attributeのタイプ</h3><p><code>knife exec</code>でnormalを指定していますが、chefのAttributeには他にもdefaultやoverrideなどの種類があります。defaultで記録してしまうと、次にconvergeしたタイミングで消えるので注意してください。normalであればconvergeでは消えません。優先順位は複雑なので公式のドキュメントを参照してください。</p><p><a href="https://docs.chef.io/attributes.html#attribute-precedence" target="_blank" rel="noopener">About Attributes — Chef Docs</a></p><h3 id="サンプルコード"><a href="#サンプルコード" class="headerlink" title="サンプルコード"></a>サンプルコード</h3><p>設定ファイル一式まとめたものを下記に置きました。</p><p><a href="https://github.com/kwhrtsk/knife_zero_example" target="_blank" rel="noopener">https://github.com/kwhrtsk/knife_zero_example</a></p><h3 id="knife-zero-1-7-1以前-蛇足"><a href="#knife-zero-1-7-1以前-蛇足" class="headerlink" title="knife-zero 1.7.1以前(蛇足)"></a>knife-zero 1.7.1以前(蛇足)</h3><p><code>knife_zero.host</code> にbootstrapで接続した時のIPアドレスが記録されるようになったのはknife-zero 1.8.0からです。</p><p>それ以前は <code>knife node edit</code> でjsonファイルを編集するか、下記のように<code>knife exec</code>で接続用のアドレスを追加する必要がありました。</p><pre class="line-numbers language-bash"><code class="language-bash">% ./bin/knife <span class="token function">exec</span> -E <span class="token string">"search(:node, 'hostname:host001'){|n| n.normal['chef_ip'] = ENV['VAGRANT_HOST001']; n.save}"</span>% ./bin/knife <span class="token function">exec</span> -E <span class="token string">"search(:node, 'hostname:host002'){|n| n.normal['chef_ip'] = ENV['VAGRANT_HOST002']; n.save}"</span><span class="token comment" spellcheck="true"># 設定されているか確認</span>% ./bin/knife search node <span class="token string">'hostname:*'</span> -a chef_ip2 items foundhost001.example:  chef_ip: 192.168.33.10host002.example:  chef_ip: 192.168.33.11<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>knife exec</code>でどのようなことができるかは、下記のドキュメントが参考になります。</p><ul><li><a href="https://docs.chef.io/knife_exec.html#examples" target="_blank" rel="noopener">knife exec — Chef Docs</a></li><li><a href="https://gist.github.com/ipedrazas/aadbaeb808f5ace5d3ce" target="_blank" rel="noopener">knife cheat</a></li></ul><h2 id="更新履歴"><a href="#更新履歴" class="headerlink" title="更新履歴"></a>更新履歴</h2><ul><li>2015-08-27<ul><li>本記事の内容が開発環境での利用を想定したものであることを追記</li><li><a href="https://github.com/higanworks/knife-zero/pull/57" target="_blank" rel="noopener">knife-zefo 1.8.0で追加された機能</a>に対応</li></ul></li><li>2015-08-28<ul><li>サンプルのVagrantfileに書いたboxを変更(<a href="https://atlas.hashicorp.com/chef/" target="_blank" rel="noopener">跡地</a>)<code>chef/centos-7.1 =&gt; bento/centos-7.1</code></li></ul></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Vagrantでknife-zeroを試そうとして挫折する人が多いらしいので、自分の考えるベストプラクティスを書いてみます。&lt;/p&gt;
&lt;p&gt;追記：記事の一部を更新しました。詳細は末尾の更新履歴でご確認ください。&lt;/p&gt;
&lt;p&gt;以下、Vagrantを開発環境で利用することを想定しています。&lt;/p&gt;
&lt;p&gt;下記のドキュメントを参考にしました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://qiita.com/sawanoboly/items/ae3c96734c5cee72863c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Knife-ZeroとVagrant(単品VM) - Qiita&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;TL-DR&quot;&gt;&lt;a href=&quot;#TL-DR&quot; class=&quot;headerlink&quot; title=&quot;TL;DR&quot;&gt;&lt;/a&gt;TL;DR&lt;/h2&gt;&lt;p&gt;プライベートネットワークモードで立ち上げた2つのVMに、knife-zeroでレシピを適用する具体的な手順を説明します。&lt;/p&gt;
&lt;p&gt;基本方針は下記の通り。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;コマンドラインでのパラメータを極力減らす。&lt;/li&gt;
&lt;li&gt;IPアドレスの記述は.envrcに集約して、その他の設定ファイルやコマンドラインでは環境変数を使う。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;適切に設定ファイルを書けば、下記のような一連のコマンドでレシピの適用まで実行することができます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;% cd knife_zero_example
% direnv allow
% vagrant up
% bundle install --path=vendor/bundle --binstubs
% ./bin/berks vendor cookbooks
% ./bin/knife zero bootstrap $VAGRANT_HOST001
% ./bin/knife zero bootstrap $VAGRANT_HOST002
% ./bin/knife node run_list set host001.example build-essential
% ./bin/knife node run_list set host002.example build-essential
% ./bin/knife zero converge &amp;#39;name:*.example&amp;#39; -a knife_zero.host
&lt;/code&gt;&lt;/pre&gt;
    
    </summary>
    
      <category term="開発環境" scheme="https://chopschips.net/categories/%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83/"/>
    
    
      <category term="chef" scheme="https://chopschips.net/tags/chef/"/>
    
      <category term="vagrant" scheme="https://chopschips.net/tags/vagrant/"/>
    
      <category term="knife-zero" scheme="https://chopschips.net/tags/knife-zero/"/>
    
  </entry>
  
  <entry>
    <title>技術ブログのためのHexoのテーマ Ingenuous をリリースしました</title>
    <link href="https://chopschips.net/blog/2015/08/24/hexo-theme-ingenuous/"/>
    <id>https://chopschips.net/blog/2015/08/24/hexo-theme-ingenuous/</id>
    <published>2015-08-24T11:42:53.000Z</published>
    <updated>2017-10-23T13:42:11.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="http://chopschips.net/blog/2015/08/23/migrate-to-hexo-from-octopress/">先日ブログエンジンをHexoに移行した</a>のですが、その際に下記のページのテーマをひと通り試してみました。</p><ul><li><a href="https://hexo.io/themes/" target="_blank" rel="noopener">Themes | Hexo</a></li><li><a href="http://js.romareas.net/demo/" target="_blank" rel="noopener">Hexo Templates Garelly</a></li><li><a href="https://github.com/hexojs/hexo/wiki/Themes" target="_blank" rel="noopener">Themes · hexojs/hexo Wiki</a></li></ul><p>しかしどれもしっくりこなかったので、デフォルトのテーマ <a href="https://github.com/hexojs/hexo-theme-landscape" target="_blank" rel="noopener">Landscape</a> をベースにして自分好みのものを作りました。</p><p>変更がそれなりの分量になったので、Ingenuousと名前をつけてリリースすることにしました。</p><p><a href="https://github.com/kwhrtsk/hexo-theme-ingenuous" target="_blank" rel="noopener">kwhrtsk/hexo-theme-ingenuous</a></p><p>欲しい機能を追加したり、スタイルを部分的に自分好みに修正したりしています。基本方針は「自分の技術ブログに必要十分な機能とスタイル」です。</p><p>主な追加機能・変更箇所は下記の通り。</p><ul><li>モバイルの画面で、コードブロックにより多くのコードが表示されるように修正</li><li>サイドバーにAboutウィジェットを追加</li><li>モバイルのナビゲーションメニューにサイドバー(モバイルでの表示時は画面下部へ移動)のアイテムへジャンプするリンクを追加</li><li>投稿ページにシェア数を表示</li><li>記事内にTOCを表示する機能追加(front-matterで<code>toc: true</code>を指定)</li></ul><p>あとは過去記事のページネーションなど細かい修正をいくつか。</p><p>オリジナルのLandscapeはMIT License、IngenuousもMIT Licenseです。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;a href=&quot;http://chopschips.net/blog/2015/08/23/migrate-to-hexo-from-octopress/&quot;&gt;先日ブログエンジンをHexoに移行した&lt;/a&gt;のですが、その際に下記のページのテーマをひと通り試してみました。&lt;/
      
    
    </summary>
    
      <category term="制作物" scheme="https://chopschips.net/categories/%E5%88%B6%E4%BD%9C%E7%89%A9/"/>
    
    
      <category term="hexo" scheme="https://chopschips.net/tags/hexo/"/>
    
      <category term="blog" scheme="https://chopschips.net/tags/blog/"/>
    
  </entry>
  
  <entry>
    <title>OctopressからHexoへの移行</title>
    <link href="https://chopschips.net/blog/2015/08/23/migrate-to-hexo-from-octopress/"/>
    <id>https://chopschips.net/blog/2015/08/23/migrate-to-hexo-from-octopress/</id>
    <published>2015-08-23T14:20:00.000Z</published>
    <updated>2017-10-23T13:42:11.000Z</updated>
    
    <content type="html"><![CDATA[<p>ブログエンジンを<a href="http://octopress.org/" target="_blank" rel="noopener">Octopress</a>から<a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a>へ移行しました。</p><p>この記事では自分の場合の移行の手順についてまとめます。</p><a id="more"></a><h2 id="コンテンツデータの移行"><a href="#コンテンツデータの移行" class="headerlink" title="コンテンツデータの移行"></a>コンテンツデータの移行</h2><p>記事や画像などのコンテンツデータの移行は、自分の場合はほぼこれだけでOKでした。</p><pre><code># 新しいhexoのルートディレクトリを作成mkdir hexocd hexohexo init# 投稿記事をコピーcp -pr ../octopress/source/_posts/*.markdown source/_posts/# 拡張子をmdに変更for f in *.markdown; do echo mv $f ${f:r}.md; done | sh -x# 投稿の先頭行(`---`)を削除for f in source/_posts/*.md; do sed -i &quot;&quot; -e &#39;1,1d&#39; $f; done# 画像をコピーcp -pr ../octopress/source/images source/# その他のファイルをコピー(CNAMEは独自ドメインの場合のみ)cp -p ../octopress/public/{CNAME,favicon.ico} source/</code></pre><p>ただし、Octopressの <code>include</code> に相当する機能はHexoには無いので、その記事についてはベタッとinclude先をコピーしました。</p><h2 id="設定ファイルの編集-config-yml"><a href="#設定ファイルの編集-config-yml" class="headerlink" title="設定ファイルの編集(_config.yml)"></a>設定ファイルの編集(<code>_config.yml</code>)</h2><p>従来の投稿のパーマリンク、および今後のパーマリンクの構造をOctopressと同じにするため、<code>_config.yml</code> で下記のように設定を変更しました。</p><pre><code>permalink: blog/:year/:month/:day/:title/new_post_name: :year-:month-:day-:title.md</code></pre><p>デプロイ先はGitHubなので、下記の設定を追加しました。</p><pre><code>deploy:  type: git  repo: git@github.com:kwhrtsk/kwhrtsk.github.io.git</code></pre><p>テーマはしっくり来るものがなかったので、Landscapeをベースに自作したものを使っています。見た目はほとんどLandscapeのままですが、特にモバイルでのコードの表示量や、サイドバーにAbout追加したり細かい修正を加えています。</p><p>これについては別の記事で。</p><p><a href="http://chopschips.net/blog/2015/08/24/hexo-theme-ingenuous/">技術ブログのためのHexoのテーマ Ingenuous をリリースしました</a></p><h2 id="プラグインの追加"><a href="#プラグインの追加" class="headerlink" title="プラグインの追加"></a>プラグインの追加</h2><p>下記のプラグインを追加しました。</p><ul><li><code>hexo-generator-feed</code><ul><li>/atom.xmlの出力のため</li></ul></li><li><code>hexo-generator-sitemap</code><ul><li>/sitemap.xmlの出力のため</li></ul></li><li><code>hexo-generator-robotstxt</code><ul><li>/robots.txtの出力のため</li></ul></li><li><code>hexo-deployer-git</code><ul><li>GitHubへのデプロイのため</li></ul></li></ul><pre><code>npm install --save         \  hexo-generator-feed      \  hexo-generator-sitemap   \  hexo-generator-robotstxt \  hexo-deployer-git</code></pre><p>ここまでやれば、次のコマンドでローカルにサーバを立ち上げて、<a href="http://localhost:4000/" target="_blank" rel="noopener">http://localhost:4000/</a> でプレビューを確認できます。記事や<code>_config.yml</code>の変更は随時反映されます。</p><pre><code>hexo server</code></pre><p>デプロイは下記のコマンドです。</p><pre><code>hexo deploy</code></pre><p><code>.deploy_git/</code> ディレクトリ以下にデプロイ用のリポジトリが作られて、<code>_config.yml</code>で指定したリポジトリにpushされます。(octopressのコミットはすべて消えます)</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;ブログエンジンを&lt;a href=&quot;http://octopress.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Octopress&lt;/a&gt;から&lt;a href=&quot;https://hexo.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hexo&lt;/a&gt;へ移行しました。&lt;/p&gt;
&lt;p&gt;この記事では自分の場合の移行の手順についてまとめます。&lt;/p&gt;
    
    </summary>
    
      <category term="Blog" scheme="https://chopschips.net/categories/Blog/"/>
    
    
      <category term="octopress" scheme="https://chopschips.net/tags/octopress/"/>
    
      <category term="hexo" scheme="https://chopschips.net/tags/hexo/"/>
    
      <category term="node" scheme="https://chopschips.net/tags/node/"/>
    
  </entry>
  
  <entry>
    <title>RailsのエラーモニタリングツールErrbitのChefレシピ</title>
    <link href="https://chopschips.net/blog/2015/04/27/errbit/"/>
    <id>https://chopschips.net/blog/2015/04/27/errbit/</id>
    <published>2015-04-27T14:00:00.000Z</published>
    <updated>2015-08-23T14:19:54.000Z</updated>
    
    <content type="html"><![CDATA[<p>4/17に大阪で開催されたRailsの勉強会でLTしてきました。</p><ul><li><a href="https://riding-rails.doorkeeper.jp/events/22793" target="_blank" rel="noopener">Railsをのりこなせ！ - Riding Rails勉強会</a></li><li><a href="http://www.techscore.com/blog/2015/04/23/riding-rails-vol1-yattayo/" target="_blank" rel="noopener">» Riding Rails 勉強会 #1 Rails をのりこなせ！やりました！発表資料も！ TECHSCORE BLOG</a></li></ul><p>自分の発表資料をちょっと加筆修正したものを以下に公開しました。</p><p><iframe src="https://www.slideshare.net/slideshow/embed_code/key/oDPA7cITu1ZJBy" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="https://www.slideshare.net/secret/oDPA7cITu1ZJBy" title="Railsのエラーログとの付き合い方" target="_blank">Railsのエラーログとの付き合い方</a> </strong> from <strong><a href="http://www.slideshare.net/kwhrtsk" target="_blank">Taisuke Kawahara</a></strong> </div></p><p>テーマはRailsのエラーモニタリングです。</p><p>通知先はSlackという前提で、<a href="https://github.com/smartinez87/exception_notification" target="_blank" rel="noopener">exception_notification</a>というgemで直接Slackに通知する方法と、New RelicやAirbrakeといったWebサービス経由で通知する方法、<a href="https://github.com/errbit/errbit" target="_blank" rel="noopener">Errbit</a> というOSSのAirbrakクローン経由でSlackに通知する方法を紹介しました。</p><p>ErrbitはHerokuの無料枠で運用することが可能で、詳細なセットアップの情報が日本語でも見つかります。</p><p><a href="http://morizyun.github.io/blog/errbit-heroku-rails-error-ruby/" target="_blank" rel="noopener">Errbit - Railsアプリの本番エラーをherokuで管理、メール通知する【無料枠】 - 酒と泪とRubyとRailsと</a></p><p>今回はHerokuを使わずにオンプレやAmazon EC2のようなIaaSに自分でセットアップする方法を検討しました。</p><p>まずErrbitをセットアップするChefのレシピを探してみたのですが、見つかったものは対応しているErrbitのバージョンが古く、サポートもUbuntuのみでした。</p><p><a href="https://supermarket.chef.io/cookbooks/errbit" target="_blank" rel="noopener">errbit Cookbook - Chef Supermarket</a></p><p>自分はCentOSも好きなので、2015-04-17時点の最新のErrbitに対応していて、かつUbuntuとCentOSのどちらもサポートしているものを作成して公開しました。</p><p><a href="https://supermarket.chef.io/cookbooks/errbit-server" target="_blank" rel="noopener">errbit-server Cookbook - Chef Supermarket</a></p><p>今回はコミュニティクックブックを多めに使ってやってみました。大まかな構成は下記のとおりです。</p><ul><li>コミュニティクックブックでやっていること<ul><li>mongodbのインストール</li><li>rbenvとruby_buildのインストール</li><li>ruby 2.2.2のインストール(attributeで変更可能)</li><li>Errbitのチェックアウトと初期化<ul><li>applicationクックブックを利用</li><li>application_rubyはマイグレーションでbundlerのrakeを使おうとせずにエラーになるのでスルー</li></ul></li><li>ErrbitにバンドルされているUnicornをservice_factoryでサービス化(SysV or Upstart)</li><li>Unicornのlogrotate</li></ul></li><li>自前で追加した分<ul><li>Errbitのデプロイ先のディレクトリレイアウト</li><li>Errbitの初期化処理(<code>rake errbit:bootstrap</code>)</li><li>ErrbitのUnicornを起動するためのラッパ作成</li><li>Errbitの設定値のための環境変数をattributeで設定する仕組み</li></ul></li></ul><p>Vagrantで動作を確認するための手順をsampleディレクトリに置いています。下記のバージョンの組み合わせで動作を確認しています。</p><ul><li>OSX Yosemite</li><li>Ruby 2.2.2</li><li>Vagrant 1.7.2</li><li>VirtualBox 4.3.26</li><li>CentOS 6.6 or Ubuntu 14.10</li></ul><p>CentOSで試すのであれば、ターミナルで下記のようなコマンドを入力すると、最終的に <code>http://192.168.33.10:3000/</code> でローカルVM上のErrbitにログインできます。</p><a id="more"></a><pre class="line-numbers language-bash"><code class="language-bash"><span class="token function">git</span> clone https://github.com/kwhrtsk/chef-errbit-server<span class="token function">cd</span> chef-errbit-server/sample<span class="token comment" spellcheck="true"># VMのIPアドレスを環境変数 VAGRANT_PRIVATE_NETWORK_IP に設定</span><span class="token function">source</span> .envrc<span class="token comment" spellcheck="true"># chefなど必要なgemをインストール</span>bundle <span class="token function">install</span> --path<span class="token operator">=</span>vendor/bundlebundle binstubs berkshelf chef<span class="token comment" spellcheck="true"># errbit-serverと依存クックブックをダウンロード</span>./bin/berks vendor cookbooks/<span class="token comment" spellcheck="true"># VM起動</span>vagrant up<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>レシピの適用はknife-zeroかknife-soloのいずれかを好みで。</p><pre><code># knife-zeroの場合./bin/knife zero bootstrap --sudo -x vagrant -i .vagrant/machines/default/virtualbox/private_key $VAGRANT_PRIVATE_NETWORK_IP -N $VAGRANT_PRIVATE_NETWORK_IP./bin/knife node from file node.json./bin/knife zero chef_client &quot;name:*&quot; -a chef_ip -x vagrant --identity-file ./.vagrant/machines/default/virtualbox/private_key --sudo# knife-soloの場合./bin/knife solo bootstrap $VAGRANT_PRIVATE_NETWORK_IP --identity-file .vagrant/machines/default/virtualbox/private_key -x vagrant./bin/knife node run_list add $VAGRANT_PRIVATE_NETWORK_IP errbit-server./bin/knife solo cook -i .vagrant/machines/default/virtualbox/private_key vagrant@$VAGRANT_PRIVATE_NETWORK_IP</code></pre><p>Ubuntuで試したい場合はVagrantfileの下記の部分を書き換えてください。</p><pre><code>  config.vm.box = &quot;chef/centos-6.6&quot;  #config.vm.box = &quot;chef/ubuntu-14.10&quot;</code></pre><p>Errbitの組み込みの初期化処理は <code>errbit:bootstrap</code> というrakeタスクです。このタスクを実行するとErrbitの初期ユーザが作成されるのですが、パスワードは毎回ランダムに生成され、標準出力に出力されます。このレシピではその出力を <code>/opt/errbit/bootstrap.out</code> に記録しています。</p><p>レシピの適用が終わったら、下記のコマンドを実行して初期ユーザの認証情報を確認して下さい。</p><pre><code>vagrant ssh -- cat /opt/errbit/bootstrap.out</code></pre><p>こんな内容です。</p><pre><code>Seeding database-------------------------------Creating an initial admin user:-- email:    errbit@errbit.example.com-- password: Lwc_FBV81Z2zBe sure to note down these credentials now!</code></pre><p>SlackのWebhookはSlackの管理画面で作成することができます。</p><img src="/images/post_20150427_errbit/slack1.png"><img src="/images/post_20150427_errbit/slack2.png"><img src="/images/post_20150427_errbit/slack3.png"><img src="/images/post_20150427_errbit/slack4.png"><p>ここで作ったURLをErrbitのアプリ登録画面で入力します。</p><img src="/images/post_20150427_errbit/errbit1.png"><p>登録すると下記のように自分のRailsアプリに置く設定ファイル(config/initializers/errbit.rb)が表示されます。</p><img src="/images/post_20150427_errbit/errbit2.png"><p>Gemfileに <code>gem &#39;airbrake&#39;</code> を追加してRAILS_ENV=productionで例外を発生させれば、Errbitにエラーが登録されると同時に、slackにも通知が届くはずです。</p><p>同一のエラーが複数回届いた場合、何度目のエラーで通知するかを指定できます。chef-errbit-serverはデフォルトでは1回目、10回目、100回目に通知を行います。(メールでの通知は行わないようにしています)</p><p>このあたりの設定はattributeで変更できるようにしています。詳細はドキュメントを参照してください。</p><ul><li><a href="https://supermarket.chef.io/cookbooks/errbit-server" target="_blank" rel="noopener">https://supermarket.chef.io/cookbooks/errbit-server</a></li><li><a href="https://github.com/errbit/errbit/blob/master/docs/configuration.md" target="_blank" rel="noopener">https://github.com/errbit/errbit/blob/master/docs/configuration.md</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;4/17に大阪で開催されたRailsの勉強会でLTしてきました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://riding-rails.doorkeeper.jp/events/22793&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Railsをのりこなせ！ - Riding Rails勉強会&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.techscore.com/blog/2015/04/23/riding-rails-vol1-yattayo/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;» Riding Rails 勉強会 #1 Rails をのりこなせ！やりました！発表資料も！ TECHSCORE BLOG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の発表資料をちょっと加筆修正したものを以下に公開しました。&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://www.slideshare.net/slideshow/embed_code/key/oDPA7cITu1ZJBy&quot; width=&quot;427&quot; height=&quot;356&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt; &lt;/iframe&gt; &lt;div style=&quot;margin-bottom:5px&quot;&gt; &lt;strong&gt; &lt;a href=&quot;https://www.slideshare.net/secret/oDPA7cITu1ZJBy&quot; title=&quot;Railsのエラーログとの付き合い方&quot; target=&quot;_blank&quot;&gt;Railsのエラーログとの付き合い方&lt;/a&gt; &lt;/strong&gt; from &lt;strong&gt;&lt;a href=&quot;http://www.slideshare.net/kwhrtsk&quot; target=&quot;_blank&quot;&gt;Taisuke Kawahara&lt;/a&gt;&lt;/strong&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;テーマはRailsのエラーモニタリングです。&lt;/p&gt;
&lt;p&gt;通知先はSlackという前提で、
&lt;a href=&quot;https://github.com/smartinez87/exception_notification&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;exception_notification&lt;/a&gt;というgemで直接Slackに通知する方法と、
New RelicやAirbrakeといったWebサービス経由で通知する方法、
&lt;a href=&quot;https://github.com/errbit/errbit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Errbit&lt;/a&gt; というOSSのAirbrakクローン経由でSlackに通知する方法を紹介しました。&lt;/p&gt;
&lt;p&gt;ErrbitはHerokuの無料枠で運用することが可能で、詳細なセットアップの情報が日本語でも見つかります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://morizyun.github.io/blog/errbit-heroku-rails-error-ruby/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Errbit - Railsアプリの本番エラーをherokuで管理、メール通知する【無料枠】 - 酒と泪とRubyとRailsと&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;今回はHerokuを使わずにオンプレやAmazon EC2のようなIaaSに自分でセットアップする方法を検討しました。&lt;/p&gt;
&lt;p&gt;まずErrbitをセットアップするChefのレシピを探してみたのですが、見つかったものは対応しているErrbitのバージョンが古く、サポートもUbuntuのみでした。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://supermarket.chef.io/cookbooks/errbit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;errbit Cookbook - Chef Supermarket&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;自分はCentOSも好きなので、2015-04-17時点の最新のErrbitに対応していて、かつUbuntuとCentOSのどちらもサポートしているものを作成して公開しました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://supermarket.chef.io/cookbooks/errbit-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;errbit-server Cookbook - Chef Supermarket&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;今回はコミュニティクックブックを多めに使ってやってみました。大まかな構成は下記のとおりです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;コミュニティクックブックでやっていること&lt;ul&gt;
&lt;li&gt;mongodbのインストール&lt;/li&gt;
&lt;li&gt;rbenvとruby_buildのインストール&lt;/li&gt;
&lt;li&gt;ruby 2.2.2のインストール(attributeで変更可能)&lt;/li&gt;
&lt;li&gt;Errbitのチェックアウトと初期化&lt;ul&gt;
&lt;li&gt;applicationクックブックを利用&lt;/li&gt;
&lt;li&gt;application_rubyはマイグレーションでbundlerのrakeを使おうとせずにエラーになるのでスルー&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ErrbitにバンドルされているUnicornをservice_factoryでサービス化(SysV or Upstart)&lt;/li&gt;
&lt;li&gt;Unicornのlogrotate&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;自前で追加した分&lt;ul&gt;
&lt;li&gt;Errbitのデプロイ先のディレクトリレイアウト&lt;/li&gt;
&lt;li&gt;Errbitの初期化処理(&lt;code&gt;rake errbit:bootstrap&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;ErrbitのUnicornを起動するためのラッパ作成&lt;/li&gt;
&lt;li&gt;Errbitの設定値のための環境変数をattributeで設定する仕組み&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vagrantで動作を確認するための手順をsampleディレクトリに置いています。下記のバージョンの組み合わせで動作を確認しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OSX Yosemite&lt;/li&gt;
&lt;li&gt;Ruby 2.2.2&lt;/li&gt;
&lt;li&gt;Vagrant 1.7.2&lt;/li&gt;
&lt;li&gt;VirtualBox 4.3.26&lt;/li&gt;
&lt;li&gt;CentOS 6.6 or Ubuntu 14.10&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CentOSで試すのであれば、ターミナルで下記のようなコマンドを入力すると、最終的に &lt;code&gt;http://192.168.33.10:3000/&lt;/code&gt; でローカルVM上のErrbitにログインできます。&lt;/p&gt;
    
    </summary>
    
      <category term="制作物" scheme="https://chopschips.net/categories/%E5%88%B6%E4%BD%9C%E7%89%A9/"/>
    
    
      <category term="ruby" scheme="https://chopschips.net/tags/ruby/"/>
    
      <category term="chef" scheme="https://chopschips.net/tags/chef/"/>
    
      <category term="errbit" scheme="https://chopschips.net/tags/errbit/"/>
    
  </entry>
  
  <entry>
    <title>Sidekiqのコードに潜む愉快なメソッドたち</title>
    <link href="https://chopschips.net/blog/2015/04/04/funcy-methods-in-sidekiq/"/>
    <id>https://chopschips.net/blog/2015/04/04/funcy-methods-in-sidekiq/</id>
    <published>2015-04-04T13:50:00.000Z</published>
    <updated>2017-10-23T13:42:11.000Z</updated>
    
    <content type="html"><![CDATA[<p><br></p><p>少し前にRails4.2のActiveJobのバックエンドについて調べました。</p><p><a href="http://chopschips.net/blog/2015/02/26/active-job/">Resque、SidekiqからSucker PunchまでActiveJobのバックエンドについてひと通り調べてみた</a></p><p>このとき<a href="https://github.com/mperham/sidekiq" target="_blank" rel="noopener">Sidekiq</a>のコードを読んで、<a href="https://github.com/celluloid/celluloid" target="_blank" rel="noopener">Celluloid</a>によるアクターモデルで並行処理を実装していることを知りました。ちょうどScalaやErlangでアクターを使う方法を調べていたところだったので興味がわき、Celluloidを利用したアプリケーションのサンプルとして読んでみることにしました。</p><p>その時見つけた愉快なメソッドを紹介します。</p><p>今回読んだSidekiqのバージョンは3.3.3です。</p><h2 id="注意事項"><a href="#注意事項" class="headerlink" title="注意事項"></a>注意事項</h2><ul><li>本エントリはSidekiqやアクターの解説記事ではありません。</li><li>記事に機種依存文字が含まれています。<ul><li>OSXのChromeとFirefox、iOSのSafariとChromeでのみ表示を確認しています。</li><li>iOSのブラウザではGithubから引用しているコードの一部が化けます。</li></ul></li></ul><a id="more"></a><h3><code style="font-family: monospace;"> Sidekiq::Queue#💣 </code></h3><p>SidekiqはRedisをバックエンドとしてジョブを管理・実行するプロダクトです。</p><p><code>Sidekiq::Queue</code>はそのジョブキューを扱うためのクラスで、💣はキューをクリアするためのメソッドです。</p><p>もしも使うのであれば次のような字面になります。</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token keyword">require</span> <span class="token string">'sidekiq/api'</span>queue <span class="token operator">=</span> <span class="token constant">Sidekiq</span><span class="token punctuation">:</span><span class="token symbol">:Queue</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token class-name">queue<span class="token punctuation">.</span></span>💣<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>このメソッドはSidekiq::Queue#clearのエイリアスとして、以下で定義されています。  </p><p><a href="https://github.com/mperham/sidekiq/blob/v3.3.3/lib/sidekiq/api.rb#L246-L254" target="_blank" rel="noopener">https://github.com/mperham/sidekiq/blob/v3.3.3/lib/sidekiq/api.rb#L246-L254</a></p><script src="http://gist-it.appspot.com/github/mperham/sidekiq/raw/v3.3.3/lib/sidekiq/api.rb?slice=245:253"></script><p>なかなかハジけたメソッド名ですよね。</p><p>なおSidekiq::SortedSetにも同様のメソッドがあります。</p><h3><code style="font-family: monospace;"> Sidekiq::CLI#☠ </code></h3><p>exitのエイリアスとして定義されています。</p><p><a href="https://github.com/mperham/sidekiq/blob/v3.3.3/lib/sidekiq/cli.rb#L205" target="_blank" rel="noopener">https://github.com/mperham/sidekiq/blob/v3.3.3/lib/sidekiq/cli.rb#L205</a></p><script src="http://gist-it.appspot.com/github/mperham/sidekiq/raw/v3.3.3/lib/sidekiq/cli.rb?slice=203:204"></script><p>exitはSidekiq::CLIには定義されていないので、この場合Kernel.exitが実行されます。</p><p>exitは由緒正しいメソッド名ですが、実行すればこのプロセスは死ぬわけですから、dieの方がより率直に動作を表現していると言えます。ただ、<code>☠</code>まで行くと毒薬かもしれないし海賊かもしれないため意図が曖昧になっており、好ましくないと思います。</p><h3><code style="font-family: monospace;"> Sidekiq::Manager#❤ </code></h3><p>なんとSidekiq::Manager#heatbeatの中で実際に使われています。</p><p><a href="https://github.com/mperham/sidekiq/blob/v3.3.3/lib/sidekiq/manager.rb#L137-L148" target="_blank" rel="noopener">https://github.com/mperham/sidekiq/blob/v3.3.3/lib/sidekiq/manager.rb#L137-L148</a></p><script src="http://gist-it.appspot.com/github/mperham/sidekiq/raw/v3.3.3/lib/sidekiq/manager.rb?slice=136:147"></script><p>サービスインしたあとでこんな実装が動いているのを知ってしまったら、胸がドキドキするかもしれませんね。</p><h3><code style="font-size: 0.8em; font-family: Menlo, Monaco, 'Andale Mono', 'lucida console', 'Courier New', monospace;">Sidekiq.❨╯°□°❩╯︵┻━┻ </code></h3><p>さて、このメソッドは何のためのメソッドなのでしょうか。実行すると下記のメッセージが表示されます。</p><p><code>&quot;Calm down, bro&quot;</code></p><p>意訳すると、まあまあ兄さん落ち着いて、というところでしょうか。</p><p>このメソッドにはRSpecで書かれた単体テストがあります。</p><p><a href="https://github.com/mperham/sidekiq/blob/v3.3.3/test/test_sidekiq.rb#L28-L36" target="_blank" rel="noopener">https://github.com/mperham/sidekiq/blob/v3.3.3/test/test_sidekiq.rb#L28-L36</a><br>※iOSで見ると一部文字化けすると思います。</p><script src="http://gist-it.appspot.com/github/mperham/sidekiq/raw/v3.3.3/test/test_sidekiq.rb?slice=27:35"></script><p>どうやらこのメソッドは、怒れるプログラマが感情的な仕草を表現できるようにしつつ、その気持ちを宥めるために作られたメソッドのようです。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>4つの愉快なメソッドを紹介しました。</p><ul><li><code>Sidekiq::Queue#💣</code></li><li><code>Sidekiq::CLI#☠</code></li><li><code>Sidekiq::Manager#❤</code></li><li><code>Sidekiq.❨╯°□°❩╯︵┻━┻</code></li></ul><p>これらのうち、実際に内部で使われているのは<code>Sidekiq::Manager#❤</code>だけです。</p><p>SidekiqはCOVERALLSというサービスでコミットごとにテストを実行してカバレッジ計測をしており、今日時点のカバレッジは85.67%です。<code>Sidekiq::Manager#❤</code>についてもテストで最低一度は実行していることが確認されています。</p><p><a href="https://coveralls.io/builds/2264006/source?filename=lib%2Fsidekiq%2Fmanager.rb#L137" target="_blank" rel="noopener">https://coveralls.io/builds/2264006/source?filename=lib%2Fsidekiq%2Fmanager.rb#L137</a></p><p><code>Sidekiq::Queue#💣</code> と <code>Sidekiq::CLI#☠</code> はただのエイリアスで、内部では一度も使用されていません。<code>Sidekiq.❨╯°□°❩╯︵┻━┻</code>はジョークメソッドで内部でも使われていませんが、テストはちゃんと書かれていました。</p><h3 id="絵文字について"><a href="#絵文字について" class="headerlink" title="絵文字について"></a>絵文字について</h3><p><code>💣</code>、<code>❤</code> はUnicode6.0で追加された携帯電話の絵文字で、いわゆる機種依存文字です。海外でもemojiと呼ぶようです。</p><p><a href="http://www.unicode.org/Public/UCD/latest/ucd/StandardizedVariants.html" target="_blank" rel="noopener">Standardized Variants</a></p><ul><li><a href="http://www.fileformat.info/info/unicode/char/1f4a3/index.htm" target="_blank" rel="noopener">BOMB</a></li><li><a href="http://www.fileformat.info/info/unicode/char/2764/index.htm" target="_blank" rel="noopener">HEAVY BLACK HEART</a></li></ul><p><code>☠</code> も機種依存文字ですが、上の2つに比べると古い文字のようですね。</p><ul><li><a href="http://www.fileformat.info/info/unicode/char/2620/index.htm" target="_blank" rel="noopener">SKULL AND CROSSBONES</a></li></ul><p>常識的に考えると絵文字でコード書くなんて論外という方がほとんどだと思います(それでもSidekiqは素晴らしいソフトウェアだと思います)。</p><p>ただ、このたびあえて先入観を捨ててその可能性を検討してみた結果、下記の気づきがありました。</p><ul><li>Githubの検索フォームで <code>💣</code> はヒットするけど <code>❤</code>はヒットしない。</li><li>Google検索では <code>💣</code> も <code>❤</code> もヒットしない。</li><li>似たような文字がいっぱいある。 <code>❤</code> とか <code>♥</code>とか<code>❥</code>とか。</li><li>同じUnicodeの文字でもフォント次第で大幅に見た目が変わり得る。</li><li>当然だけど機種依存文字は端末によっては表示することができないことがある。</li></ul><p>少なくとも検索性がかなり悪くなるということがわかりましたので、個人的には絵文字でコード書くのは避けようと思います。</p><p>アクターの話はまた今度気が向いたら書きます。</p><p>なお私はどちらかというとResqueの方が好きです。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;少し前にRails4.2のActiveJobのバックエンドについて調べました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://chopschips.net/blog/2015/02/26/active-job/&quot;&gt;Resque、SidekiqからSucker PunchまでActiveJobのバックエンドについてひと通り調べてみた&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;このとき&lt;a href=&quot;https://github.com/mperham/sidekiq&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Sidekiq&lt;/a&gt;のコードを読んで、&lt;a href=&quot;https://github.com/celluloid/celluloid&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Celluloid&lt;/a&gt;によるアクターモデルで並行処理を実装していることを知りました。
ちょうどScalaやErlangでアクターを使う方法を調べていたところだったので興味がわき、Celluloidを利用したアプリケーションのサンプルとして読んでみることにしました。&lt;/p&gt;
&lt;p&gt;その時見つけた愉快なメソッドを紹介します。&lt;/p&gt;
&lt;p&gt;今回読んだSidekiqのバージョンは3.3.3です。&lt;/p&gt;
&lt;h2 id=&quot;注意事項&quot;&gt;&lt;a href=&quot;#注意事項&quot; class=&quot;headerlink&quot; title=&quot;注意事項&quot;&gt;&lt;/a&gt;注意事項&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;本エントリはSidekiqやアクターの解説記事ではありません。&lt;/li&gt;
&lt;li&gt;記事に機種依存文字が含まれています。&lt;ul&gt;
&lt;li&gt;OSXのChromeとFirefox、iOSのSafariとChromeでのみ表示を確認しています。&lt;/li&gt;
&lt;li&gt;iOSのブラウザではGithubから引用しているコードの一部が化けます。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
      <category term="Research" scheme="https://chopschips.net/categories/Research/"/>
    
    
      <category term="ruby" scheme="https://chopschips.net/tags/ruby/"/>
    
      <category term="sidekiq" scheme="https://chopschips.net/tags/sidekiq/"/>
    
      <category term="emoji" scheme="https://chopschips.net/tags/emoji/"/>
    
  </entry>
  
  <entry>
    <title>Rails 開発をサポートするChrome拡張 Rails Panel の機能と仕組み</title>
    <link href="https://chopschips.net/blog/2015/03/06/rails-panel/"/>
    <id>https://chopschips.net/blog/2015/03/06/rails-panel/</id>
    <published>2015-03-06T14:38:45.000Z</published>
    <updated>2015-08-23T14:11:28.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="http://www.rubyonrails365.com/7-must-have-gems-to-install-on-any-project/" target="_blank" rel="noopener">7 must have Development Gems to install on every project</a></p><p>この記事を読んでいて、Rails PanelというChrome拡張を知りました。</p><ul><li><a href="https://github.com/qqshfox/meta_request" target="_blank" rel="noopener">qqshfox/meta_request</a></li><li><a href="https://chrome.google.com/webstore/detail/railspanel/gjpfobpafnhjhbajcjgccbbdofdckggg" target="_blank" rel="noopener">RailsPanel - Chrome ウェブストア</a></li></ul><h1 id="Rails-Panelでできること"><a href="#Rails-Panelでできること" class="headerlink" title="Rails Panelでできること"></a>Rails Panelでできること</h1><p>デベロッパーツールにRailsというパネルが追加されます。このパネルでは、ページリクエスト単位で下記のような情報を表示することができます。</p><ul><li>Breakdown: 処理時間の内訳(ActiveRecord, Rendering, Other)</li><li>Params: コントローラから参照できるparamsの内容</li><li>ActiveRecord: そのリクエストを処理する際に発行したSQLと処理時間</li><li>Rendering: ビューテンプレートごとの描画時間</li></ul><img src="/images/post_20150306_rails_panel/ss_01.png"><p>要するに <code>rails server</code> の標準出力(あるいは <code>log/${RAILS_ENV}.log</code>)の情報を見やすく表示してくれます。</p><a id="more"></a><h1 id="使い方"><a href="#使い方" class="headerlink" title="使い方"></a>使い方</h1><ul><li>ChromeにはChrome ウェブストアからインストールします。<br><a href="https://chrome.google.com/webstore/detail/railspanel/gjpfobpafnhjhbajcjgccbbdofdckggg" target="_blank" rel="noopener">RailsPanel - Chrome ウェブストア</a></li><li>Railsには <code>meta_request</code> というgemを追加します。</li></ul><pre><code>group :development do  gem &quot;meta_request&quot;end</code></pre><h1 id="動作の仕組み"><a href="#動作の仕組み" class="headerlink" title="動作の仕組み"></a>動作の仕組み</h1><p>meta_requestをbundleすると、いくつかのRackミドルウェアが自動的に組み込まれます。</p><ul><li><a href="https://github.com/dejan/rails_panel/blob/master/meta_request/lib/meta_request/middlewares.rb" target="_blank" rel="noopener">rails_panel/middlewares.rb at master · dejan/rails_panel</a></li><li><a href="https://github.com/dejan/rails_panel/blob/master/meta_request/lib/meta_request/railtie.rb" target="_blank" rel="noopener">rails_panel/railtie.rb at master · dejan/rails_panel</a></li></ul><p>この中のMetaRequest::Middlewares::Headersによって <code>X-Meta-Request-Version</code>が、MetaRequest::Middlewares::RequestIdによって <code>X-Request-Id</code>がRailsアプリケーションが返すすべてのレスポンスのヘッダに追加されます。</p><ul><li><a href="https://github.com/dejan/rails_panel/blob/master/meta_request/lib/meta_request/middlewares/headers.rb" target="_blank" rel="noopener">rails_panel/headers.rb at master · dejan/rails_panel</a></li><li><a href="https://github.com/dejan/rails_panel/blob/master/meta_request/lib/meta_request/middlewares/request_id.rb" target="_blank" rel="noopener">rails_panel/request_id.rb at master · dejan/rails_panel</a></li></ul><p>Rails Panelを有効にしていると、 <code>X-Meta-Request-Version</code> がレスポンスヘッダに存在する際に、Chromeは <code>/__meta_request/${X-Request-Id}.json</code> というパスにリクエストを投げます。</p><p><a href="https://github.com/dejan/rails_panel/blob/master/rails_panel/assets/javascripts/requests.js#L12" target="_blank" rel="noopener">rails_panel/requests.js at master · dejan/rails_panel</a></p><p>このリクエストもmeta_requestのRackミドルウェアで処理され、Railsアプリ層の手前の段階で、必要な情報がjsonでレスポンスされます。</p><p>ただし、このリクエストのログもRackミドルウェア層によってRails.loggerに出力するため、Railsのログには下記のような痕跡が残ります。</p><pre><code>Started GET &quot;/__meta_request/abcd92a2-3859-4972-9708-8303fe238e9e.json&quot; for ::1 at 2015-03-06 23:45:37 +0900</code></pre><p>なお、 <code>/__meta_request/${X-Request-Id}.json</code> のレスポンスで返されるjsonファイルは、<code>tmp/data/meta_request/</code> 以下に最大10個まで保存されます。<br>該当のコードはこのあたり。</p><ul><li><a href="https://github.com/dejan/rails_panel/blob/master/meta_request/lib/meta_request/middlewares/app_request_handler.rb" target="_blank" rel="noopener">rails_panel/app_request_handler.rb at master · dejan/rails_panel</a></li><li><a href="https://github.com/dejan/rails_panel/blob/master/meta_request/lib/meta_request/storage.rb#L12" target="_blank" rel="noopener">rails_panel/storage.rb at master · dejan/rails_panel</a></li></ul><p>RackミドルウェアとChrome拡張でモニタリング用の機能を実装するというアプローチは他のことにも応用ができそうですね。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;http://www.rubyonrails365.com/7-must-have-gems-to-install-on-any-project/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;7 must have Development Gems to install on every project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;この記事を読んでいて、Rails PanelというChrome拡張を知りました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/qqshfox/meta_request&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;qqshfox/meta_request&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/railspanel/gjpfobpafnhjhbajcjgccbbdofdckggg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RailsPanel - Chrome ウェブストア&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;Rails-Panelでできること&quot;&gt;&lt;a href=&quot;#Rails-Panelでできること&quot; class=&quot;headerlink&quot; title=&quot;Rails Panelでできること&quot;&gt;&lt;/a&gt;Rails Panelでできること&lt;/h1&gt;&lt;p&gt;デベロッパーツールにRailsというパネルが追加されます。
このパネルでは、ページリクエスト単位で下記のような情報を表示することができます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Breakdown: 処理時間の内訳(ActiveRecord, Rendering, Other)&lt;/li&gt;
&lt;li&gt;Params: コントローラから参照できるparamsの内容&lt;/li&gt;
&lt;li&gt;ActiveRecord: そのリクエストを処理する際に発行したSQLと処理時間&lt;/li&gt;
&lt;li&gt;Rendering: ビューテンプレートごとの描画時間&lt;/li&gt;
&lt;/ul&gt;
&lt;img src=&quot;/images/post_20150306_rails_panel/ss_01.png&quot;&gt;
&lt;p&gt;要するに &lt;code&gt;rails server&lt;/code&gt; の標準出力(あるいは &lt;code&gt;log/${RAILS_ENV}.log&lt;/code&gt;)の情報を見やすく表示してくれます。&lt;/p&gt;
    
    </summary>
    
      <category term="Research" scheme="https://chopschips.net/categories/Research/"/>
    
    
      <category term="ruby" scheme="https://chopschips.net/tags/ruby/"/>
    
      <category term="rails" scheme="https://chopschips.net/tags/rails/"/>
    
  </entry>
  
  <entry>
    <title>Resque、SidekiqからSucker PunchまでActiveJobのバックエンドについてひと通り調べてみた</title>
    <link href="https://chopschips.net/blog/2015/02/26/active-job/"/>
    <id>https://chopschips.net/blog/2015/02/26/active-job/</id>
    <published>2015-02-26T13:11:00.000Z</published>
    <updated>2015-08-23T14:11:24.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="ActiveJobのバックエンドと、永続化先としてRedisが好まれる理由"><a href="#ActiveJobのバックエンドと、永続化先としてRedisが好まれる理由" class="headerlink" title="ActiveJobのバックエンドと、永続化先としてRedisが好まれる理由"></a>ActiveJobのバックエンドと、永続化先としてRedisが好まれる理由</h1><p>Rails 4.2で、ActiveJobというクラスが導入されました。</p><p>これ以前より、Railsで非同期処理を行う際にはResqueやSidekiq、Delayed Jobなどが広く使われていましたが、ActiveJobはジョブを記述するためのインタフェースを抽象化して、ジョブの実装を変えること無くジョブランナーを切り替えることを可能にするものです。なおActiveJobにおいては、バックエンドを指定しなければジョブは非同期実行しようとしても即座に実行されます。</p><p>Rails 4.2.0時点では、ActiveJobのバックエンドとして以下のページにある9つのいずれかを使用できます。</p><p><a href="http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html" target="_blank" rel="noopener">ActiveJob::QueueAdapters</a></p><ul><li>Backburner</li><li>Delayed Job</li><li>Qu</li><li>Que</li><li>queue_classic</li><li>Resque 1.x</li><li>Sidekiq</li><li>Sneakers</li><li>Sucker Punch</li></ul><p>ちなみにRuby ToolboxのBackground Jobsというカテゴリの上位3つは、Resque、Sidekiq、Delayed Jobでした。この3つがよく使われている印象です。</p><p><a href="https://www.ruby-toolbox.com/categories/Background_Jobs" target="_blank" rel="noopener">The Ruby Toolbox - Background Jobs: Save jobs in a queue and process them later wihout blocking your current thread</a></p><p>特にResque、Sidekiqは、ジョブの情報を永続化する先としてRedisを必須としています。</p><p>情報の保存先ならActiveRecord(RDBMS)でも良さそうなものですが、あえてRedisを必須としている理由は何だろうか考えてみました。</p><p>ぱっと思いつくのは、「キューにジョブが積まれた」というイベントをワーカーへ伝えるための手段として、Redisの<a href="http://redis.io/topics/pubsub" target="_blank" rel="noopener">Pub/Sub</a>や<a href="http://redis.io/commands/BLPOP" target="_blank" rel="noopener">BLPOP</a>/<a href="http://redis.io/commands/brpop" target="_blank" rel="noopener">BRPOP</a>のようなメッセージング機構を使うのが簡単で都合が良かったのではないか？ということです。</p><p>バックエンドにMySQLのようなRDBMSを使う場合、普通に考えるとワーカー側はジョブを保存したテーブルをロックしつつポーリングしないといけないのでスケールしなさそうだし、RabbitMQのようなメッセージングサービスを用いるのと比べて、RedisであればRailsエンジニアは元々better memcachedとして使用しているケースが多いので導入のハードルが低いです。</p><p>というわけで、このエントリではResque、Sidekiqの2つについてはRedisのメッセージング機構を使っているかどうかを確認しつつ、ActiveJobのページに挙げられている9つのプロダクトについてジョブ情報の保存先を調べてまとめてみようと思います。</p><p>9つの中で特にSucker Punchはジョブの保存先もワーカープロセスの起動も必要とせずに非同期処理を行えるので、簡単に作りたいならおすすめです。詳細は後述。</p><a id="more"></a><h1 id="Resque"><a href="#Resque" class="headerlink" title="Resque"></a>Resque</h1><p>Rails 4.2.0時点においてActiveJobが対応しているのはRedisのバージョン1系列です。</p><p><a href="https://github.com/resque/resque/tree/1-x-stable" target="_blank" rel="noopener">resque/resque at 1-x-stable</a></p><p>ワーカーを起動する場合はrakeタスクを実行するので、そのあたりからコードを読んでいきます。</p><p><a href="https://github.com/resque/resque/blob/1-x-stable/lib/tasks/resque.rake" target="_blank" rel="noopener">lib/tasks/resque.task</a>これはresque/tasksを読み込んでいるだけ。</p><p><a href="https://github.com/resque/resque/blob/1-x-stable/lib/resque/tasks.rb#L42" target="_blank" rel="noopener">resque/tasks.rb</a><code>Resque::Worker#work</code>がメインループのようです。</p><p><a href="https://github.com/resque/resque/blob/1-x-stable/lib/resque/worker.rb#L205" target="_blank" rel="noopener">resque/worker.rb</a>ジョブが無い場合はインターバル分だけスリープする実装になっていました。ジョブの取得はさらに読んでいくとRedisの<code>LPOP</code>を使っていて、キューが空でもブロックせずにnilが帰ってきます。</p><pre><code>Resque::Worker#work  Resque#reserve    Resque::Job.reserve      Resque.pop        Redis#lpop(redis-3.2.1)</code></pre><h2 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h2><p>Resqueのバージョン1系はRedisのメッセージング機構は使わずにポーリングしてました。</p><p>ただ、2015-02-26時点のmasterブランチのHEADでは、インターバルに設定した値をタイムアウト値として<code>BLPOP</code>を使う実装になっていました。(インターバルが1より小さい場合はnon-blockで実質ポーリング)</p><h1 id="Sidekiq"><a href="#Sidekiq" class="headerlink" title="Sidekiq"></a>Sidekiq</h1><p>現時点で最新の3.3.2を読んでいきます。</p><p><a href="https://github.com/mperham/sidekiq/tree/v3.3.2" target="_blank" rel="noopener">mperham/sidekiq at v3.3.2</a></p><p>Sidekiqのエントリポイントはbin/sidekiqです。中身はSidekiq::CLI#runなのでそこを見てみます。</p><p><a href="https://github.com/mperham/sidekiq/blob/v3.3.2/lib/sidekiq/cli.rb#L76" target="_blank" rel="noopener">sidekiq/cli.rb</a></p><p>Sidekiq::Launcher#runがメインループのようです。</p><p><a href="https://github.com/mperham/sidekiq/blob/v3.3.2/lib/sidekiq/launcher.rb#L39" target="_blank" rel="noopener">sidekiq/launcher.rb</a></p><p>Celluloidを使ったアクターモデルでFetcherとManagerとPollerという3つのインスタンスが強調しつつ並行処理を行っているように見えます。Pollerがスケジュールされたジョブをワークキューに載せ替える、ということをやっていました。Managerの方では同時実行数の数だけProcessorのインスタンスを作って、ProcessorごとにFetcher#fetchしています。Fetcherの中身をざっくり追いかけると、<a href="https://github.com/mperham/sidekiq/blob/v3.3.2/lib/sidekiq/fetch.rb#L102" target="_blank" rel="noopener">sidekiq/fetch.rb</a></p><pre><code>Fetcher#fetch  Fetcher.strategy    Fetcher::BasicFetch#retrive_work      Redis#brpop</code></pre><p>という流れで最終的にRedisの<code>BRPOP</code>でジョブ情報を読みだしていました。</p><h2 id="結論-1"><a href="#結論-1" class="headerlink" title="結論"></a>結論</h2><p>Sidekiqはポーリングせずに、Redisの<code>BRPOP</code>を使ってジョブが積まれたイベントをワーカーに通知していました。</p><h1 id="その他のバックエンド"><a href="#その他のバックエンド" class="headerlink" title="その他のバックエンド"></a>その他のバックエンド</h1><h2 id="Backburner"><a href="#Backburner" class="headerlink" title="Backburner"></a>Backburner</h2><p><a href="https://github.com/nesquena/backburner" target="_blank" rel="noopener">nesquena/backburner</a></p><p><a href="http://kr.github.io/beanstalkd/" target="_blank" rel="noopener">beanstalkd</a> というジョブキューサーバをバックエンドに使うようです。</p><h2 id="Delayed-Job"><a href="#Delayed-Job" class="headerlink" title="Delayed Job"></a>Delayed Job</h2><p><a href="https://github.com/collectiveidea/delayed_job" target="_blank" rel="noopener">collectiveidea/delayed_job</a></p><p>バックエンドとして複数のプロダクトを指定できます。(DJはDelayed Jobのこと)</p><ul><li>ActiveRecord (DJ 3.0+)</li><li>DataMapper</li><li>IronMQ</li><li>Mongoid</li><li>MongoMapper</li><li>MongoMapper (DJ 3.0+, MongoMapper 0.11.0+)</li><li>Redis (DJ 3.0+, experimental)<a href="https://github.com/collectiveidea/delayed_job/wiki/Backends" target="_blank" rel="noopener">Backends · collectiveidea/delayed_job Wiki</a></li></ul><h2 id="Qu"><a href="#Qu" class="headerlink" title="Qu"></a>Qu</h2><p><a href="https://github.com/bkeepers/qu" target="_blank" rel="noopener">bkeepers/qu</a></p><p>RedisかMongo。</p><h2 id="Que"><a href="#Que" class="headerlink" title="Que"></a>Que</h2><p><a href="https://github.com/chanks/que" target="_blank" rel="noopener">chanks/que</a></p><p>PostgreSQLの<a href="http://www.postgresql.org/docs/current/static/explicit-locking.html#ADVISORY-LOCKS" target="_blank" rel="noopener">Advisory Lock</a>を使います。</p><p>Advisory Lockを用いてMessage Queueを実現する方法については下記のサイトが詳しいです。</p><p><a href="http://d.hatena.ne.jp/n_shuyo/20090415/mq" target="_blank" rel="noopener">PostgreSQL で簡易に MQ - Mi manca qualche giovedi`?</a></p><p>ちなみにMySQLでMessage Queueやる場合はQ4MというストレージエンジンがありますがActiveJobで使う方法は見つかりませんでした。</p><h2 id="queue-classic"><a href="#queue-classic" class="headerlink" title="queue_classic"></a>queue_classic</h2><p><a href="https://github.com/QueueClassic/queue_classic" target="_blank" rel="noopener">QueueClassic/queue_classic</a></p><p>PostgreSQL。listen / notify &amp; row lockingで実装。PostgreSQLはPub/Sub機能があるんですね。</p><h2 id="Sneakers"><a href="#Sneakers" class="headerlink" title="Sneakers"></a>Sneakers</h2><p><a href="https://github.com/jondot/sneakers" target="_blank" rel="noopener">jondot/sneakers</a></p><p>RabbitMQ。サンプルコードでRedisを使っていますが、デモのためのカウントアップをRedisでやっているだけです。</p><h2 id="Sucker-Punch"><a href="#Sucker-Punch" class="headerlink" title="Sucker Punch"></a>Sucker Punch</h2><p>ジョブ情報の保存先やワーカープロセスの立ち上げが必要ありません。Celluloidで即座に非同期処理を開始するモデルのようですね。RailsやSinatraのようなアプリケーションプロセス一つでジョブの実行が(非同期に)完結するというのがウリみたいです。</p><p>そのため、RedisやRabbitMQを用意する必要はないし、ワーカープロセスを起動する必要すらありません。</p><p>Herokuで安価にサービス運用したいのでRedisやRabbitMQは使いたくないけどビューのレスポンスは高速化したいような場合に使うと良さそうです。個人的にはこれがActiveJobのデフォルトのバックエンドでも良いくらいだと思いました。</p><p>development環境のWebrickで試してみましたが、コントローラのアクションメソッドで<code>TestJob.perform_later</code>しても、アクションそのものはすぐにレスポンスが返ってきました。また、ジョブが完了していなくても次のリクエストを処理することができていました。Celluloid使っているらしいのでジョブはThreadで実行されていると思われます。</p><p>以下動作を確認したサンプルコードです。</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token comment" spellcheck="true"># config/routes.rb</span><span class="token constant">Rails</span><span class="token punctuation">.</span>application<span class="token punctuation">.</span>routes<span class="token punctuation">.</span>draw <span class="token keyword">do</span>  get <span class="token string">'top/welcome'</span>  get <span class="token string">'top/job'</span>  root <span class="token string">'top#welcome'</span><span class="token keyword">end</span><span class="token comment" spellcheck="true"># config/initializers/sucker_punch.rb</span><span class="token constant">Rails</span><span class="token punctuation">.</span>application<span class="token punctuation">.</span>configure <span class="token keyword">do</span>  config<span class="token punctuation">.</span>active_job<span class="token punctuation">.</span>queue_adapter <span class="token operator">=</span> <span class="token symbol">:sucker_punch</span><span class="token keyword">end</span><span class="token comment" spellcheck="true"># app/jobs/test_job.rb</span><span class="token keyword">class</span> <span class="token class-name">TestJob</span> <span class="token operator">&lt;</span> <span class="token constant">ActiveJob</span><span class="token punctuation">:</span><span class="token symbol">:Base</span>  queue_as <span class="token symbol">:default</span>  <span class="token keyword">def</span> <span class="token function">perform</span><span class="token punctuation">(</span><span class="token operator">*</span>args<span class="token punctuation">)</span>    logger<span class="token punctuation">.</span>info <span class="token string">"job started"</span>    sleep <span class="token number">5</span>    logger<span class="token punctuation">.</span>info <span class="token string">"job finished"</span>  <span class="token keyword">end</span><span class="token keyword">end</span><span class="token comment" spellcheck="true"># app/controllers/top_controller.rb</span><span class="token keyword">class</span> <span class="token class-name">TopController</span> <span class="token operator">&lt;</span> <span class="token constant">ApplicationController</span>  <span class="token keyword">def</span> welcome  <span class="token keyword">end</span>  <span class="token keyword">def</span> job    <span class="token constant">TestJob</span><span class="token punctuation">.</span>perform_later    flash<span class="token punctuation">[</span><span class="token symbol">:notice</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"done!"</span>    redirect_to top_welcome_path  <span class="token keyword">end</span><span class="token keyword">end</span><span class="token comment" spellcheck="true"># app/views/welcome.view.erb</span><span class="token operator">&lt;</span><span class="token operator">%</span> <span class="token keyword">if</span> flash<span class="token punctuation">[</span><span class="token symbol">:notice</span><span class="token punctuation">]</span> <span class="token string">%>  &lt;%= flash[:notice] %></span><span class="token operator">&lt;</span><span class="token operator">%</span> <span class="token keyword">end</span> <span class="token string">%>&lt;%= link_to "Do job", top_job_path %></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="感想"><a href="#感想" class="headerlink" title="感想"></a>感想</h1><ul><li>resque 1系がRedisを使いつつ実はポーリングしていたのが意外でした。</li><li>PostgreSQLはほとんど使ったこと無いんだけど、RDBMSでPub/Subできたら楽できそう。</li><li>Sucker Punchはワーカープロセスの起動無しで非同期処理できる点が便利。これでOKという場面は結構ありそうなので良い発見でした。</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;ActiveJobのバックエンドと、永続化先としてRedisが好まれる理由&quot;&gt;&lt;a href=&quot;#ActiveJobのバックエンドと、永続化先としてRedisが好まれる理由&quot; class=&quot;headerlink&quot; title=&quot;ActiveJobのバックエンドと、永続化先としてRedisが好まれる理由&quot;&gt;&lt;/a&gt;ActiveJobのバックエンドと、永続化先としてRedisが好まれる理由&lt;/h1&gt;&lt;p&gt;Rails 4.2で、ActiveJobというクラスが導入されました。&lt;/p&gt;
&lt;p&gt;これ以前より、Railsで非同期処理を行う際にはResqueやSidekiq、Delayed Jobなどが広く使われていましたが、
ActiveJobはジョブを記述するためのインタフェースを抽象化して、
ジョブの実装を変えること無くジョブランナーを切り替えることを可能にするものです。
なおActiveJobにおいては、バックエンドを指定しなければジョブは非同期実行しようとしても即座に実行されます。&lt;/p&gt;
&lt;p&gt;Rails 4.2.0時点では、ActiveJobのバックエンドとして以下のページにある9つのいずれかを使用できます。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ActiveJob::QueueAdapters&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Backburner&lt;/li&gt;
&lt;li&gt;Delayed Job&lt;/li&gt;
&lt;li&gt;Qu&lt;/li&gt;
&lt;li&gt;Que&lt;/li&gt;
&lt;li&gt;queue_classic&lt;/li&gt;
&lt;li&gt;Resque 1.x&lt;/li&gt;
&lt;li&gt;Sidekiq&lt;/li&gt;
&lt;li&gt;Sneakers&lt;/li&gt;
&lt;li&gt;Sucker Punch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ちなみにRuby ToolboxのBackground Jobsというカテゴリの上位3つは、Resque、Sidekiq、Delayed Jobでした。この3つがよく使われている印象です。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.ruby-toolbox.com/categories/Background_Jobs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The Ruby Toolbox - Background Jobs: Save jobs in a queue and process them later wihout blocking your current thread&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;特にResque、Sidekiqは、ジョブの情報を永続化する先としてRedisを必須としています。&lt;/p&gt;
&lt;p&gt;情報の保存先ならActiveRecord(RDBMS)でも良さそうなものですが、あえてRedisを必須としている理由は何だろうか考えてみました。&lt;/p&gt;
&lt;p&gt;ぱっと思いつくのは、「キューにジョブが積まれた」というイベントをワーカーへ伝えるための手段として、
Redisの&lt;a href=&quot;http://redis.io/topics/pubsub&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Pub/Sub&lt;/a&gt;や&lt;a href=&quot;http://redis.io/commands/BLPOP&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BLPOP&lt;/a&gt;/&lt;a href=&quot;http://redis.io/commands/brpop&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BRPOP&lt;/a&gt;のようなメッセージング機構を使うのが簡単で都合が良かったのではないか？ということです。&lt;/p&gt;
&lt;p&gt;バックエンドにMySQLのようなRDBMSを使う場合、普通に考えるとワーカー側はジョブを保存したテーブルをロックしつつポーリングしないといけないのでスケールしなさそうだし、
RabbitMQのようなメッセージングサービスを用いるのと比べて、RedisであればRailsエンジニアは元々better memcachedとして使用しているケースが多いので導入のハードルが低いです。&lt;/p&gt;
&lt;p&gt;というわけで、このエントリではResque、Sidekiqの2つについてはRedisのメッセージング機構を使っているかどうかを確認しつつ、
ActiveJobのページに挙げられている9つのプロダクトについてジョブ情報の保存先を調べてまとめてみようと思います。&lt;/p&gt;
&lt;p&gt;9つの中で特にSucker Punchはジョブの保存先もワーカープロセスの起動も必要とせずに非同期処理を行えるので、簡単に作りたいならおすすめです。詳細は後述。&lt;/p&gt;
    
    </summary>
    
      <category term="Research" scheme="https://chopschips.net/categories/Research/"/>
    
    
      <category term="ruby" scheme="https://chopschips.net/tags/ruby/"/>
    
      <category term="rails" scheme="https://chopschips.net/tags/rails/"/>
    
  </entry>
  
  <entry>
    <title>Mac(Yosemite)にMySQL 5.6とmysql2 gemをインストールする方法</title>
    <link href="https://chopschips.net/blog/2015/02/15/macports-mysql2-gem/"/>
    <id>https://chopschips.net/blog/2015/02/15/macports-mysql2-gem/</id>
    <published>2015-02-15T14:55:00.000Z</published>
    <updated>2016-03-11T14:17:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>Mac(OSX 10.10: Yosemite, 10.11: El Capitan)にMySQL 5.6とmysql2 gemをインストールする方法のメモです。<br>MacPortsを使う方法と、Oracleが配布しているコミュニティ版を使う方法について述べます。</p><p>追記(2016-03-11): El Capitanの場合の手順を追加(コミュニティ版のみ確認)</p><a id="more"></a><h1 id="MacPortsを使う場合"><a href="#MacPortsを使う場合" class="headerlink" title="MacPortsを使う場合"></a>MacPortsを使う場合</h1><h2 id="MySQL-5-6-server-のインストール"><a href="#MySQL-5-6-server-のインストール" class="headerlink" title="MySQL 5.6(server)のインストール"></a>MySQL 5.6(server)のインストール</h2><pre><code>% sudo port install mysql56-server</code></pre><h2 id="mysql2-gem-のインストール"><a href="#mysql2-gem-のインストール" class="headerlink" title="mysql2(gem)のインストール"></a>mysql2(gem)のインストール</h2><pre><code># bundle コマンドを使う場合% bundle config build.mysql2 --with-mysql-config=/opt/local/lib/mysql56/bin/mysql_config% bundle install# gem コマンドを使う場合% gem install mysql2 -- --with-mysql-config=/opt/local/lib/mysql56/bin/mysql_config</code></pre><h1 id="MySQL-Community-Serverを使う場合"><a href="#MySQL-Community-Serverを使う場合" class="headerlink" title="MySQL Community Serverを使う場合"></a>MySQL Community Serverを使う場合</h1><p><a href="http://dev.mysql.com/downloads/mysql/" target="_blank" rel="noopener">http://dev.mysql.com/downloads/mysql/</a></p><p>ここからOSX 10.9用のdmgをダウンロードしてインストール。</p><p>システム環境設定パネルでMySQL Serverを起動します。(MySQLのインストール後にOSXの再起動が必要な場合があるようです)</p><img src="/images/post_20150215_mysql/ss_01.png"><img src="/images/post_20150215_mysql/ss_02.png"><p>gemをインストールする前にシンボリックリンクを貼る必要があります。</p><pre><code># Yosemiteの場合sudo ln -s /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/lib/libmysqlclient.18.dylib# El Capitanの場合sudo ln -s /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/local/lib/libmysqlclient.18.dylib</code></pre><p>これを行わないと、gemのインストールは成功しますが、実行時に下記のようなエラーが発生します。</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token comment" spellcheck="true"># test.rb</span><span class="token keyword">require</span> <span class="token string">'mysql2'</span>client <span class="token operator">=</span> <span class="token constant">Mysql2</span><span class="token punctuation">:</span><span class="token symbol">:Client</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span><span class="token symbol">:host</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token string">"localhost"</span><span class="token punctuation">,</span> <span class="token symbol">:username</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token string">"root"</span><span class="token punctuation">)</span>puts client<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span><span class="token string">"show databases"</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre><code>% ruby test.rb/Users/kawahara_taisuke/.rbenv/versions/2.2.0/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require&#39;: dlopen(/Users/kawahara_taisuke/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/extensions/x86_64-darwin-14/2.2.0-static/mysql2-0.3.17/mysql2/mysql2.bundle, 9): Library not loaded: libmysqlclient.18.dylib (LoadError)  Referenced from: /Users/kawahara_taisuke/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/extensions/x86_64-darwin-14/2.2.0-static/mysql2-0.3.17/mysql2/mysql2.bundle  Reason: image not found - /Users/kawahara_taisuke/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/extensions/x86_64-darwin-14/2.2.0-static/mysql2-0.3.17/mysql2/mysql2.bundle        from /Users/kawahara_taisuke/.rbenv/versions/2.2.0/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require&#39;        from /Users/kawahara_taisuke/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/mysql2-0.3.17/lib/mysql2.rb:8:in `&lt;top (required)&gt;&#39;        from /Users/kawahara_taisuke/.rbenv/versions/2.2.0/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:128:in `require&#39;        from /Users/kawahara_taisuke/.rbenv/versions/2.2.0/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:128:in `rescue in require&#39;        from /Users/kawahara_taisuke/.rbenv/versions/2.2.0/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:39:in `require&#39;        from test.rb:1:in `&lt;main&gt;&#39;</code></pre><p>MySQLのディストリビューションパッケージの古いバグの名残のようです。</p><p>参考:</p><ul><li><a href="http://bugs.mysql.com/bug.php?id=61243" target="_blank" rel="noopener">http://bugs.mysql.com/bug.php?id=61243</a></li><li><a href="http://stackoverflow.com/questions/5446747/ruby-mysql2-gem-not-working-mac-os-x-snow-leopard-ruby-1-9-2" target="_blank" rel="noopener">http://stackoverflow.com/questions/5446747/ruby-mysql2-gem-not-working-mac-os-x-snow-leopard-ruby-1-9-2</a></li></ul><p>gemのインストールでは特別なことをする必要はありません。</p><pre><code># bundle コマンドを使う場合% bundle install# gem コマンドを使う場合% gem install mysql2</code></pre><p>bundleを使う場合、前述のMacPorts用の設定を行っていると~/.bundle/configに下記の行が追加されているので、 削除しておく必要があります。</p><pre><code># ~/.bundle/configBUNDLE_BUILD__MYSQL2: &quot;--with-mysql-config=/opt/local/lib/mysql56/bin/mysql_config&quot;</code></pre><h3 id="MySQL-Serverのメモリ使用量が多い"><a href="#MySQL-Serverのメモリ使用量が多い" class="headerlink" title="MySQL Serverのメモリ使用量が多い"></a>MySQL Serverのメモリ使用量が多い</h3><p>素のまま使っていると起動直後から400MBくらい割り当てられます。<br>下記のページを参考にして、 <code>table_definition_cache</code> というパラメータを適切に設定すれば抑えることができます。</p><p><a href="http://d.hatena.ne.jp/mabots/20140526/1401101070" target="_blank" rel="noopener">Macbook Air にいれた開発用 MySQL 5.6.xx の メモリ使用量が異様に多い - 知のレバレッジを最大化せよ</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Mac(OSX 10.10: Yosemite, 10.11: El Capitan)にMySQL 5.6とmysql2 gemをインストールする方法のメモです。&lt;br&gt;MacPortsを使う方法と、Oracleが配布しているコミュニティ版を使う方法について述べます。&lt;/p&gt;
&lt;p&gt;追記(2016-03-11): El Capitanの場合の手順を追加(コミュニティ版のみ確認)&lt;/p&gt;
    
    </summary>
    
      <category term="開発環境" scheme="https://chopschips.net/categories/%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83/"/>
    
    
      <category term="ruby" scheme="https://chopschips.net/tags/ruby/"/>
    
      <category term="MySQL" scheme="https://chopschips.net/tags/MySQL/"/>
    
      <category term="Mac" scheme="https://chopschips.net/tags/Mac/"/>
    
      <category term="Yosemite" scheme="https://chopschips.net/tags/Yosemite/"/>
    
  </entry>
  
  <entry>
    <title>Chef 12のオープンソース版と25ノード制限について</title>
    <link href="https://chopschips.net/blog/2014/09/10/chef-12/"/>
    <id>https://chopschips.net/blog/2014/09/10/chef-12/</id>
    <published>2014-09-09T15:10:00.000Z</published>
    <updated>2015-08-23T14:11:17.000Z</updated>
    
    <content type="html"><![CDATA[<p>Chef バージョン12がリリースされました。</p><p>ところで下記のニュースで気になる記述がありました。</p><blockquote><p>ビジネスモデル面でも変更があり、オープンソース版（「Chef Essentials」）と商用版（「Chef Enterprise」）を単一のコードベースに統一し、新たにフリーミアムサブスクリプションモデルを導入した。新たに導入された無料の「Chef Essentials」プランでは、25ノードまで（ホステッド版では5ノードまで）の設定管理が可能となっている。</p><footer><strong>Source Forge.JP Magazine</strong><cite><a href="http://sourceforge.jp/magazine/14/09/10/064900" target="_blank" rel="noopener">ITインフラ自動化ツール「Chef 12」がリリース、オープンソース版と商用版でコードを統一化</a></cite></footer></blockquote><p><strong>オープンソース版のライセンスが変わって、25ノードまでの台数制限が入った</strong>ようにも読めます。</p><p><strong>マジか。</strong></p><p>さすがにそれは無いだろうと思ったけど念の為に本家の情報を確認。</p><p><a href="http://www.getchef.com/chef/#plans-and-pricing" target="_blank" rel="noopener">Chef | IT automation for speed and awesomeness | Chef</a></p><p>上記のページより、ライセンスと価格についての表示を引用します。</p><img src="/images/post_20140910_chef_12/ss_01.png"><p>CHEF Essentialsというのがオープンソース版ですね。ここに下記のような記述があります。</p><blockquote><p>Premium Features</p><p>・Access to premium features for up to 25 nodes・Hosting available for up to 5 nodes</p><footer><strong>Chef</strong><cite><a href="http://www.getchef.com/chef/#plans-and-pricing" target="_blank" rel="noopener">Plans and Pricing</a></cite></footer></blockquote><p>つまり25ノードに制限されるのは、Essentials版そのものではなく、SubscriptionとEnterprise Subscriptionの欄に書かれている<strong>Premium Features</strong>だけです。</p><blockquote><p>Premium Features</p><p>・Management console with reporting and role-based access control・Analytics platform・High availability・Replication・Available as hosted service</p></blockquote><p>以下の機能はEssentials版でも無制限に利用できます。</p><pre><code>✔ Chef server    ✔ Manage 10,000+ Nodes with a single Chef server    ✔ Maintain a searchable blueprint of your infrastructure    ✔ Easy Installation with Omnibus installer✔ Chef client    ✔ Easily manage Linux, Windows, Mac OS, Solaris, and FreeBSD✔ Chef development kit    ✔ Everything you need to start managing applications and infrastructure with Chef    ✔ Download and install in just a few clicks✔ Integration with all major cloud providers✔ Easily manage containers in a versionable, testable and repeatable way. </code></pre><p>ありえないと思ったんだけどドキドキした。</p><p>何か勘違いがあれば <a href="https://twitter.com/KawaharaTaisuke" target="_blank" rel="noopener">@KawaharaTaisuke</a> 宛までご指摘いただけましたら幸いです。</p><h2 id="リンク"><a href="#リンク" class="headerlink" title="リンク"></a>リンク</h2><ul><li><a href="http://sourceforge.jp/magazine/14/09/10/064900" target="_blank" rel="noopener">ITインフラ自動化ツール「Chef 12」がリリース、オープンソース版と商用版でコードを統一化 | SourceForge.JP Magazine</a></li><li><a href="http://www.getchef.com/chef/#plans-and-pricing" target="_blank" rel="noopener">Chef | IT automation for speed and awesomeness | Chef</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Chef バージョン12がリリースされました。&lt;/p&gt;
&lt;p&gt;ところで下記のニュースで気になる記述がありました。&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;ビジネスモデル面でも変更があり、
オープンソース版（「Chef Essentials」）と商用版（「Chef Enterp
      
    
    </summary>
    
      <category term="Research" scheme="https://chopschips.net/categories/Research/"/>
    
    
      <category term="chef" scheme="https://chopschips.net/tags/chef/"/>
    
  </entry>
  
  <entry>
    <title>knife solo prepareでエラー</title>
    <link href="https://chopschips.net/blog/2014/09/09/chef-alpha/"/>
    <id>https://chopschips.net/blog/2014/09/09/chef-alpha/</id>
    <published>2014-09-08T15:32:43.000Z</published>
    <updated>2015-08-17T15:41:33.000Z</updated>
    
    <content type="html"><![CDATA[<p>knife-soloをインストールしたら何故かchef 12.0.0.alphaがインストールされてしまいました。</p><pre><code>$ gem install knife-soloFetching: chef-zero-2.2.gem (100%)Successfully installed chef-zero-2.2Fetching: chef-12.0.0.alpha.1.gem (100%)Successfully installed chef-12.0.0.alpha.1Thanks for installing knife-solo!If you run into any issues please let us know at:  https://github.com/matschaffer/knife-solo/issuesIf you are upgrading knife-solo please uninstall any old versions byrunning `gem clean knife-solo` to avoid any errors.See http://bit.ly/CHEF-3255 for more information on the knife bugthat causes this.Successfully installed knife-solo-0.4.2Parsing documentation for chef-12.0.0.alpha.1Installing ri documentation for chef-12.0.0.alpha.1Parsing documentation for chef-zero-2.2Installing ri documentation for chef-zero-2.2Parsing documentation for knife-solo-0.4.2Done installing documentation for chef, chef-zero, knife-solo after 20 seconds3 gems installed</code></pre><p>opscodeのchefのインストーラはまだ未対応なのか、<code>knife solo prepare</code>が失敗するようになってしまいました。</p><a id="more"></a><pre><code>$ knife solo prepare centos65Bootstrapping Chef...  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                 Dload  Upload   Total   Spent    Left  Speed100 16519  100 16519    0     0  13977      0  0:00:01  0:00:01 --:--:-- 30761Downloading Chef 12.0.0.alpha.1 for el...downloading https://www.opscode.com/chef/metadata?v=12.0.0.alpha.1&amp;prerelease=false&amp;nightlies=false&amp;p=el&amp;pv=6&amp;m=x86_64  to file /tmp/install.sh.5840/metadata.txttrying wget...ERROR 404Unable to retrieve a valid package!Please file a bug report at http://tickets.opscode.comProject: ChefComponent: PackagesLabel: OmnibusVersion: 12.0.0.alpha.1Please detail your operating system type, version and any other relevant detailsMetadata URL: https://www.opscode.com/chef/metadata?v=12.0.0.alpha.1&amp;prerelease=false&amp;nightlies=false&amp;p=el&amp;pv=6&amp;m=x86_64</code></pre><p>chefがインストールされていない状態で<code>gem install knife-solo</code>した場合だけ、chefの12.0.0.alphaがインストールされるようです。</p><p>Gemfileを書いて<code>bundle install</code>した場合と、<code>gem install chef</code>した場合は11.16.0がインストールされることを確認。変なdependencyのがslip outしたんだろうか。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;knife-soloをインストールしたら何故かchef 12.0.0.alphaがインストールされてしまいました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gem install knife-solo
Fetching: chef-zero-2.2.gem (100%)
Successfully installed chef-zero-2.2
Fetching: chef-12.0.0.alpha.1.gem (100%)
Successfully installed chef-12.0.0.alpha.1
Thanks for installing knife-solo!

If you run into any issues please let us know at:
  https://github.com/matschaffer/knife-solo/issues

If you are upgrading knife-solo please uninstall any old versions by
running `gem clean knife-solo` to avoid any errors.

See http://bit.ly/CHEF-3255 for more information on the knife bug
that causes this.
Successfully installed knife-solo-0.4.2
Parsing documentation for chef-12.0.0.alpha.1
Installing ri documentation for chef-12.0.0.alpha.1
Parsing documentation for chef-zero-2.2
Installing ri documentation for chef-zero-2.2
Parsing documentation for knife-solo-0.4.2
Done installing documentation for chef, chef-zero, knife-solo after 20 seconds
3 gems installed
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;opscodeのchefのインストーラはまだ未対応なのか、&lt;code&gt;knife solo prepare&lt;/code&gt;が失敗するようになってしまいました。&lt;/p&gt;
    
    </summary>
    
    
      <category term="chef" scheme="https://chopschips.net/tags/chef/"/>
    
  </entry>
  
</feed>
