tag:crieit.net,2005:https://crieit.net/users/sgykfjsm/feed shigeの投稿 - Crieit Crieitでユーザーshigeによる最近の投稿 2019-12-19T03:13:07+09:00 https://crieit.net/users/sgykfjsm/feed tag:crieit.net,2005:PublicArticle/15624 2019-12-19T03:09:13+09:00 2019-12-19T03:13:07+09:00 https://crieit.net/posts/2019-5dfa6b49987e8 2019年を振り返るポエム <p>この記事はcrieitの<a href="https://crieit.net/advent-calendars/2019/crieit">なんでも Advent Calendar 2019</a>の19日目の記事です。前日は<a href="https://crieit.net/users/hammhiko">hammhiko</a>さんによる<a href="https://crieit.net/posts/localhost">localhost監獄</a>でした。localhostに自分専用のアプリが増殖していくのはプログラマあるあるですね!こういったアプリケーションは世に出ることは無いとしても自分の経験や成長につながりますし、なんていうか、盆栽みたいな感じで自分好みに魔改造していくのが楽しいですよね。</p> <p>さてさて、本当ならこの記事には今やっているKubernetesとかGoとかPythonがらみのネタを書こうかなーと思ってたけど、どうにも記事が煮詰まらないまま今日を迎えてしまいました。ということで、ここはポエム…というか、今年1年の振り返りをしようかなと思います。</p> <hr /> <p>自社は地方都市にあり企業の規模はまぁまぁ大きいWeb系企業です。そんなところで、私はいまは小さいチーム(本当に小さい。メンバーの異動と退職が相次いで今はなんと2人!もはやチームではなくてペアと呼んだほうが正しい)のマネージャーをしている。チームの役割としては、インフラのためのアプリケーションを開発したり運用したりしている。個人としては、これまでのプログラマの仕事に加えて、マネージャー業、特に採用活動もやっている。採用活動というのは本当に大変で、自分のチームにフィットする人というのは予想以上にいなかったりする。また、これまでのプログラマとしての仕事が減ることなく、というかむしろ増え続けていて、振り返ってみるとそれなりに消耗した一年になってしまったように思います。</p> <p>突然ですが、今って空前絶後の人手不足じゃないですか。それは弊社でも同じで全然人が採用できない!というかそもそも応募が無い!わけあって、外部のリクルーターを使うのをやめてしまったため、自分で候補者を探す必要があるんだけど、ちょっと知り合ったやつの口車に乗って選考に応募する奇特な人など見つかるはずもなく…。自分の個人的な人脈とかを駆使しても、なかなか応募まで至らなかったりする…人望をどこかに置いてきてしまったらしい…</p> <p>ということで、そもそもなんでこんなに応募が無いのか、あるいは応募してもらうことができてないのかということをここ最近(数ヶ月以上)ずっと考えている。たぶん色々理由はあって、うちに来たらどのくらいのお賃金がもらえるよとか、どういうことをやってもらいたいと思っているよ、とかそういう具体的な話ができないことが多いからということがある。もちろん、ある程度のことは話せるんだけど、なかなか相手に刺さるようなことが言えない。まぁこれは私の会話力が低いからなんだろうけど…</p> <p>いずれにしてもこのまま採用活動しててもなかなかうまいこといかないなーというのが今年の反省。来年はもっとカンファレンスでの登壇とかを増やして自社の知名度をあげるのと、海外遠征も視野に入れて採用活動をしないといけないかもなーと思っている。なにせ地方には人がいない!地元からの応募があれば嬉しいんだけど、なかなかマッチするひとがいない…</p> <hr /> <p>次に技術的な振り返り。去年もそうだったけど、今年もたくさんコードを書いた。なにせ人手不足なものでね…アラフォーですよ、私…35歳定年説はどこいった。まぁそれはおいといて、今年はKubernetesを実戦で使うことができてサービス投入までできたのでそれは良かったなー。まぁ私が関わったのは最初の立ち上げのところまでで、今は別のメンバーが運用しているんだけど…Kubernetesに関しては運用のほうが圧倒的に大変で、だけど、学ぶところが多いのでインフラレイヤーに興味がある人は絶対に触っておいたほうが良い。まぁでも世の中的にKubernetesが必要なサービスってどのくらいあるのか、言うほどKubernetesが必要になることって無いんじゃないかなとは思うけど。もっと簡単に問題が解決できるなら、Kubernetesは無理に使わなくてもいいと思う。Ref: <a target="_blank" rel="nofollow noopener" href="https://endler.dev/2019/maybe-you-dont-need-kubernetes/">Maybe You Don't Need Kubernetes</a></p> <p>多分、今年一番書いたコードはGoだと思う。自分でも意外だったけど、GoでWebアプリケーションを書いたことがあんまりなくって、初めて<a target="_blank" rel="nofollow noopener" href="https://echo.labstack.com/">echo</a>というフレームワークを使った。Flaskとかfalconみたいなマイクロフレームワークを使ったことがある人ならすんなり使えるんじゃないかなと思う。ただ、個人的な感想としては、Goで複雑なモデル操作が必要なアプリケーションは書かないほうがいいんじゃないかなって思う。やっぱりPythonみたいな動的言語と比べると、コード量が増えがちだし、面倒ではある。もちろん型付言語としてのメリットを享受することはできるんだけど。まぁでもGoは楽しい言語だし、使い勝手が良い言語なので、これからもGoをたくさん書くんだろうなーと思う。Goで面白いトピックはなんかあったけなーと思ったけど、面白い話題はだいたい<a target="_blank" rel="nofollow noopener" href="https://dave.cheney.net/">Dave Cheneyのブログ</a>にあるので、これを暇なときに読んでいれば良いと思う。</p> <hr /> <p>えー、次はPythonですかね。Pythonでは別のWebアプリケーションを作った。なんか最近は<a target="_blank" rel="nofollow noopener" href="https://responder.readthedocs.io/en/latest/">responder</a>とかいうWebフレームワークが流行っているらしく、開発者はrequestsとかpipenvを作った人らしい。ということで、まず間違いなく近いうちに主流になってそうなんだけど、色々あってDjangoを使った。というのは別にどうでも良いんだけど、今回は<a target="_blank" rel="nofollow noopener" href="https://pastedeploy.readthedocs.io/en/latest/">Paste Deployment</a>を紹介したい。</p> <p>Paste Deploymentは設定管理のためのソフトウェアみたいなやつで、アプリケーションの設定をスクリプトじゃなくて宣言的に管理することを目的に開発されたもの。また、こいつはDjangoが起動させるWSGIアプリケーションを読み込むことができるんだけど、HTTPリクエストをpipelineで処理させることができる。どういうことか簡単に言うと、自分のアプリケーションに来る前(あるいは後)に別のアプリケーションをフィルターとして置いて、ユーザーからのHTTPリクエストを事前/事後に処理することができる。</p> <p>これの何が嬉しいのか?それは認証処理のような、本来アプリケーションのビジネスロジックとは無関係なものを自分のアプリケーションから切り出すことができる。それにより、自分のアプリケーションは本来集中すべき処理に集中することができるので、余計なロジックを持たなくて良くなる。これにより、テストが格段に書きやすくなる。認証処理を考慮してテストを書くのは本当に面倒で、OAuth的な認証処理とかだったりするときはこういったやり方がとても便利。フィルターを利用したWebフレームワーク自体はそんなに珍しくないし、むしろ古くからあるんだけど、自分のアプリケーションから切り離して利用することができるのは結構珍しいんじゃないかなと思う。どっかで話すことができたら良いな。</p> <hr /> <p>(なんか文字数がこのあたりで2,700を超えてきたので、そろそろまとめに入ろう…)</p> <p>そんなこんなで技術的には色々挑戦できて学びが多かったけど、いかんせん業務量が多くてつらぽよだった…特に採用活動は本当にどうにかしないといけなくて、来年はどうにか解決したい。あと、せっかくcrieitに記事をポツポツと投稿していることだし、個人開発のビッグウェーブに乗りたいところ。アイデアはボチボチあるけど、時間を作らないと…来年はゆとりある暮らしをしたいですな…</p> <p>ということで、3,000字を超えそうなので、このへんで終わりにします。明日は<a href="https://crieit.net/users/kin-mi">きんみ</a>さんです!お楽しみに!</p> shige tag:crieit.net,2005:PublicArticle/15548 2019-11-14T13:34:28+09:00 2019-11-25T17:52:46+09:00 https://crieit.net/posts/Go-Response-Body 手軽にGoのResponse.Bodyを再利用したいとき <p>こんなコードがあるとする。</p> <pre><code class="go">... response, _ := http.Get(requestURL) body, _ := ioutil.ReadAll(response.Body) body.Close() ... </code></pre> <p>GoのHTTP Responseを<code>ioutil.ReadAll</code>なんかで読み込むと、<code>response.Body</code>のコンテンツをどこまで読み込んだかという情報を持つポインタ(Seeker)が最後まで進んでしまうので、もう1度<code>response.Body</code>を読み込もうと思っても何も読み込めない。なので、<code>response.Body</code>を別の場所にコピーしておく必要があるんだけど、適当に<code>foo = response.Body</code>とかやってもダメ。</p> <p>そんなときは<code>io.TeeReader</code>を使って別の場所にコピーする(というかバッファを読み込むためのReaderをもう1つ用意する)。そうすれば新しいSeekerを使って読み込みを行えるので、<code>response.Body</code>の再利用が出来る。</p> <pre><code class="go">var b bytes.Buffer _ = io.TeeReader(response.Body, &b) // こんな感じでコンテンツを読み込んでも後続の処理では引き続き`response.Body`を利用することが出来る。 log.Printf("DEBUG: %s", b.String()) </code></pre> <p>おしまい。</p> shige tag:crieit.net,2005:PublicArticle/15473 2019-10-11T01:39:47+09:00 2019-10-11T01:39:47+09:00 https://crieit.net/posts/6417df54e9cc7b5492fe9b8d2af9e43e パイプで受け取った標準入力を列ごとに読みこんで配列っぽく処理したい <p>最近、古いシェルスクリプトをリファクタリングしたので、そのときに使ったテクニックをここに残しておく。</p> <p>コマンドをパイプでつないで、最終的に出力を行単位で読み込みたいときがある。例えば、以下の<code>ls</code>コマンドの出力から一番ファイルサイズが大きいやつを選びたいとする。</p> <pre><code class="console">$ ls /tmp/ total 28 ... drwx------ 2 root wheel 64 10 8 01:25 KSDownloadAction.dFCyHsIUZR -rw-r--r-- 1 root wheel 0 10 9 23:35 wetrsmuoutpDmR5aR -rw-r--r-- 1 root wheel 0 10 9 23:36 wetrsmuoutpalIXOW ... </code></pre> <p>レガシー(というかマナーが悪い)なコードだとこんな感じになる。</p> <pre><code class="sh">#!/usr/bin/env bash set -eu OLD_IFS="${IFS}"; IFS=$'\n' max_size=0 max_filename= ls_outpu="$(\ls -l /tmp/ | sed -e '1d' | grep -v '^d')" for output in ${ls_outpu} do filesize="$(echo "${output}" | awk '{print $5}')" filename="$(echo "${output}" | awk '{print $9}')" if [ "${filesize}" -gt "${max_size}" ]; then max_size="${filesize}" max_filename="${filename}" fi done IFS="${OLD_IFS}" </code></pre> <p>やりたいことはタイトルにあるように「パイプで受け取った標準入力を列ごとに読みこんで配列っぽく処理したい」なんだけど、上のコードはいくつか良くない点がある。</p> <ol> <li><code>IFS</code>を一時的に置き換えている。</li> <li>空白区切りの行から特定位置の文字列を取り出すために<code>awk</code>を使っている。</li> <li><strong>数字</strong>を比較している。</li> </ol> <p>1つずつ説明する。</p> <h2 id="問題点と解決法"><a href="#%E5%95%8F%E9%A1%8C%E7%82%B9%E3%81%A8%E8%A7%A3%E6%B1%BA%E6%B3%95">問題点と解決法</a></h2> <h3 id="IFSを一時的に置き換えている。"><a href="#IFS%E3%82%92%E4%B8%80%E6%99%82%E7%9A%84%E3%81%AB%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%A6%E3%81%84%E3%82%8B%E3%80%82">IFSを一時的に置き換えている。</a></h3> <p>これは古いシェルスクリプトだとよく見かけるけど、このやり方はメンテナンスが非常にやりにくくなり、本当に危ないのでやめたほうが良い。<br /> まず、<code>IFS</code>はshellにおいて、文字列の<em>区切り</em>を示すもの。これを変えることで、文字列の区切りを変えることが出来る。</p> <p>通常、<code>IFS</code>は半角スペース、タブ文字、改行の3つである。ここでは<code>ls -l</code>の出力を行ごとに読み込むために文字列の区切りを改行区切りにしている(そうしなければ、出力を1行ごとに列単位で扱うことになってしまう)。そして処理が完了したら<code>IFS</code>を元に戻している。</p> <p>これ、最初に書いた人は良いけど、別の人がコードを保守するときに、この部分を見落としがちなのでバグが発生しやすくなるんだよね。コードを読む人は途中で<code>IFS</code>が変わったことを常に意識しながら読むことを強いられるので、コードの保守性が著しく悪くなる。</p> <p>よって、<code>IFS</code>の置き換えは良くないのでやめましょう。ではどうすれば良いのかと言うと、ヒアドキュメントを使って<code>read</code>で読み込めば良い。</p> <pre><code class="sh">while read line do ... done <<< "$(ls -l /tmp/ | sed -e '1d' | grep -v '^d')" </code></pre> <p>すこし見づらくなるけど、こうすることで<code>IFS</code>の置き換えが不要になり、行ごとに変数<code>line</code>の中に文字列が格納されるようになる。</p> <p>もしかしたらこうしたほうが良いかも?と思う人がいるかもしれない。</p> <pre><code class="sh">ls -l /tmp/ | sed -e '1d' | grep -v '^d' | while read line do ... done </code></pre> <p>べつにこれでも良いのだけど、パイプで繋がれた処理はサブシェルで起動するということを思い出してほしい。今回のようにwhileのループ文の中で、事前に定義した変数を読み込みたい場合、つまり、以下のような場合、</p> <pre><code class="sh">max_size=0 max_filename= ls -l /tmp/ | sed -e '1d' | grep -v '^d' | while read line do filesize="$(echo "${output}" | awk '{print $5}')" filename="$(echo "${output}" | awk '{print $9}')" if [ "${filesize}" -gt "${max_size}" ]; then max_size="${filesize}" max_filename="${filename}" fi done </code></pre> <p>ループ文中の<code>max_size</code>と<code>max_filename</code>は先に定義したものとは別の変数として扱われる。その上、サブシェルなのでループ文を抜けると、その中にあった変数たちは参照できなくなる。なぜなら、サブシェルはメインのシェルとは違う世界なので。よって、パイプでつなぐのでなく、ヒアドキュメントで入力を受け取るようにしている。</p> <p>もちろん、サブシェルで起動しても問題なければパイプで繋げばいいと思うけど、ヒアドキュメントを使えばサブシェルの起動を1つ減らせるので、リソースやパフォーマンスの面からもオススメしたいやり方だったりする。</p> <h3>空白区切りの行から特定位置の文字列を取り出すために<code>awk</code>を使っている。</h3> <p><code>awk</code>って便利なのでよく使われているんだけど、大抵の場合、空白切りの文字列の特定の位置の文字を取り出すときに使われることが多い。手元のコマンドラインで使うとかだったら良いんだけど、データ処理とかバッチ処理のスクリプトで使っているとしたら、なるべく次に紹介する方法で置き換えたほうが良い。<code>awk</code>は重いし、思わず色々awkの中に処理を詰め込みがちになってしまうから。</p> <p>オススメのリファクタリングとしては、<code>set</code>を使うこと。<code>set</code>というのは、よくシェルスクリプトの冒頭で<code>set -eu</code>とかやると思うんだけど、その<code>set</code>。これはbuilt-in関数なので<code>awk</code>と違って別途にプロセスがforkされることがなくて軽い。</p> <p>で、どういうふうに<code>set</code>を<code>awk</code>の代わりに使うのかというと、以下のようになる。</p> <pre><code class="sh">... set -- ${output} filesize="${5}" filename="${9}" ... </code></pre> <p>これだけ。スッキリしてわかりやすくなったと思う。解説は<code>man set</code>で見れば良いと思うけど、ここに引用しておく。</p> <blockquote> <p>Any arguments remaining after option processing are treated as values for the positional parameters and are assigned, in order, to $1, $2, ... $n.</p> </blockquote> <p><code>--</code>については以下の引用を参考。<code>grep</code>とかでもよく使うので知っている人は多いと思う。</p> <blockquote> <p>-- If no arguments follow this option, then the positional<br /> parameters are unset. Otherwise, the positional parame-<br /> ters are set to the args, even if some of them begin with<br /> a -.</p> </blockquote> <p>つまり、<code>set -- foo bar baz</code>とやると、それぞれが<code>$1</code>, <code>$2</code>, <code>$3</code>に格納される。Pythonで言うところの<code>*args</code>みたいな可変長引数みたいな感じで位置引数として利用することが出来る。これにより、もう<code>awk</code>を使ってわざわざ<code>echo</code>してパイプで…みたいなことをしなくて済む(もちろん<code>IFS</code>を変更していないことが前提である)。</p> <p>副作用としては、シェルスクリプトに渡された位置引数を上書きすることになるので、そういった引数は事前に別の変数に格納しておくのを忘れないこと。</p> <h3><strong>数字</strong>を比較している</h3> <p>これはなんのこっちゃと思うかもしれないけど、<code>-gt</code>や<code>-ne</code>は本来は<strong>数値</strong>を比較するためのものであって<strong>数字</strong>を比較するものではない。つまり、文字列のままで数字を扱うのは危ないのでやめましょうということ。ではどうするかというと、以下のようにすれば良い。</p> <pre><code class="sh"> if [ $((filesize)) -gt $((max_size)) ]; then </code></pre> <p>シェルスクリプトで足し算とかするときに<code>$((foo + bar))</code>みたいなことをするとおもんだけど(もし<code>expr</code>を使っているなら即刻書き換えましょう)、上のようにすることで数字を数値にすることが出来る。これの何が良いかと言うと、もし変数の中身がアルファベットのような数字ではない文字だった場合はゼロとして扱われる。</p> <pre><code class="console">$ a="aaa" $ echo $((a)) 0 </code></pre> <p>状況によるが、変数が数字でなければゼロとして扱って差し支えない場合はこのようにすることで余計な数字判定の式を省くことが出来る。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>ということで、ここまでの内容をまとめると以下のようになる。</p> <pre><code class="sh">max_size=0 max_filename= while read -r line do set -- ${line} filesize="${5}" filename="${9}" if [ "$((filesize))" -gt "$((max_size))" ]; then max_size="${filesize}" max_filename="${filename}" fi done <<< "$(\ls -l /tmp/ | sed -e '1d' | grep -v '^d')" </code></pre> <p>行数はほとんど変わらないけど、最初の方と比べるとわかりやすくなったかなと思う。特別な処理とかないし。ちなみに、もっとも大きいファイル名を取得するだけなら<code>ls -S | head -1</code>で一発で取れるので、わざわざこんなことをしなくて良い。</p> shige tag:crieit.net,2005:PublicArticle/15401 2019-09-17T01:00:01+09:00 2019-09-17T01:04:26+09:00 https://crieit.net/posts/2019-5d7fb18157f4f 初めて学生インターンを受け入れて感じたこと 2019夏 <h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2> <p>先月、初めて学生インターンを受け入れた。会社としては随分前から受け入れていたのだけど、うちのチームでは初めてだった。今年は縁に恵まれて受け入れることになったのだけど、自身にとって初めてだったということもあり、気づきや反省をここに残しておこうと思う。</p> <h2 id="背景"><a href="#%E8%83%8C%E6%99%AF">背景</a></h2> <p>うちのチームの特徴は以下の通り。</p> <ul> <li>会社はいわゆるWeb系の会社</li> <li>筆者のチームはインフラサービスのためのソフトウェア開発をしている</li> <li>筆者のチームは福岡が拠点だが、東京のチームと協働してサービス開発をしている</li> <li>技術スタックはソフトウェアにPythonを、サービスのインフラにはKubernetesを使っている</li> <li>日本人は筆者だけで他のメンバーは外国籍で、コミュニケーションには英語を使っている</li> </ul> <h2 id="インターン生にしてもらったこと"><a href="#%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%B3%E7%94%9F%E3%81%AB%E3%81%97%E3%81%A6%E3%82%82%E3%82%89%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">インターン生にしてもらったこと</a></h2> <p>簡単に言うと、Chaos engineeringをしてもらった。Chaos engineeringについては以下のQiitaの記事がエントリーポイントとして良さそう。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/naokiiiii/items/de20997a70922c01f754">カオスエンジニアリングと聞いてカオスになった人必見</a></li> </ul> <p>早い話が「システムに対して、有り得そうな障害を意図的に起こして問題をあぶり出し対策を考える」手法と言える。細かいというか、正確に言うと、多少違うのだけど、実際的な視点で言えば、それほど的外れではないだろう。</p> <p>で、なぜインターン生にChaos engineeringをしてもらったかというと、以下の考えに基づいている。</p> <ol> <li>Chaos engieering(に限った話ではないけど)ではいわゆる"PDCA"サイクルを回しやすく、成果や進捗が把握しやすい</li> <li>チームとしてChaos engieeringに興味を持っていたが、誰も手を付けることができていなかった</li> <li>これまでに障害対策のための実装を行ってきたが、それらが本当に機能しているかどうかを確信できていなかった</li> </ol> <p>なお、今回はテスト環境での実施となったので、本格的なChaos engineeringとは違うが、利用したソフトウェアや手法は一般的なChaos engineeringと同じものを採用した。</p> <h2 id="狙い"><a href="#%E7%8B%99%E3%81%84">狙い</a></h2> <p>まず#1 <code>Chaos engieering(に限った話ではないけど)ではいわゆる"PDCA"サイクルを回しやすく、成果や進捗が把握しやすい</code> について。Chaos engieeringを行うにあたって、対象システムをある程度把握してもらってから、どこが問題になりそうかという仮説を立てて、どのように障害を起こすかということまでを最初に考えてもらった。これはChaos engieeringを行うにあたっては非常に重要だ。というのも、Chaos engieeringは意図的にシステムを不調にさせるため、何を行うかをチームでレビューして事前に作業内容を周知する必要がある。その後、作業をしてもらって、またレポートを書いてもらい、結果をチームでレビューした。これにより、自然とPDCAサイクルが出来上がってインターン生の状況が把握しやすくなった。また、副次的な効果として、技術文書の書き方の勉強をしたり、チームメンバーとの関わりを自然に増やすことが出来たと思う。</p> <p>2つ目 <code>チームとしてChaos engieeringに興味を持っていたが、誰も手を付けることができていなかった</code> は割と重要だと思っていて、チームの誰もChaos engineeringを経験していないのでインターン生がチームでの第一人者となる。よって、周りからマサカリが飛んでくる危険を可能な限り減らして、失敗を恐れずに取り組むための環境を用意できたと思う(希望的な推測)。たとえ失敗したとしても、未経験のチームメンバーにとって全ての結果が貴重な知見となる点も重要だ。</p> <p>3つ目<code>これまでに障害対策のための実装を行ってきたが、それらが本当に機能しているかどうかを確信できていなかった</code>はインターン生のためというか、プロダクトのためなんだけど、これまでに障害で発生した問題を解決するために色々な修正や機能開発を行ってきた。しかし、同じ問題には対応できているが、同じ"ような"問題に対応できているかどうか、ということまでは確認できていなかった。類似の状況でも、特にエッジケースで、問題なく動作するかという確信が持てるかどうかはインフラレイヤーのアプリケーションでは非常に重要で、Chaos engineeringが良い機会になるだろうと思っていた。</p> <p>これらの狙いは(筆者の視点では)概ね成功したと思う。結果的に、大きな問題は顕在しなかったが、それはチームメンバーに自信を、プロダクトには信頼をもたらすことになった。</p> <h2 id="その他に気を使ったこと"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AB%E6%B0%97%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">その他に気を使ったこと</a></h2> <ul> <li>週単位で取り組みや成果物を決めて共有した <ul> <li>途中で何をして良いのかわからなくのを防ぐため</li> </ul></li> <li>途中経過や成果を発表する機会を設けた <ul> <li>成果物を発表する機会を設けることで、成果物の品質にも気を使ってもらうようにした</li> </ul></li> <li>チームメンバー以外の社員と交流する機会を設けるようにした <ul> <li>会社の雰囲気を知ってもらうために、チームメンバー以外とコミュニケーションをしてもらった</li> </ul></li> </ul> <h2 id="反省点"><a href="#%E5%8F%8D%E7%9C%81%E7%82%B9">反省点</a></h2> <ul> <li>筆者がメンター役だったが、インターンの後半では放置気味になってしまった。 <ul> <li>筆者が色々あって多忙になってしまったが、もっと気を使うべきだった。</li> <li>筆者が成果物の完成度を上げるために出来ることは色々あったが、あまり貢献することが出来なかった</li> </ul></li> <li>もっと早くチームメンバーとのコミュニケーションの機会を持てるようにするべきだった。 <ul> <li>筆者以外のチームメンバーが英語話者であったため、意図的に筆者とのコミュニケーションを多めにしていたが、いくつかの事柄に対して筆者が上手く回答できず、チームメンバーに頼ることがあった。</li> <li>これが結果としてインターン生の進捗を妨げる要因の1つになっていた。</li> <li>インターン生のコミュニケーション能力を信じて早くからチームメンバーと直接コミュニケーションできるようにするべきだった。</li> </ul></li> <li>筆者も含めてChaos engieeringは(知ってはいたが)未経験だったために適切なフォローができなかった <ul> <li>今にして思えば、手当り次第にやってみるのではなく、ある程度こちらで想定される問題や事象を整理するなどして、道筋をつけてからやってもらえば良かった</li> </ul></li> </ul> <h2 id="次回に活かせそうなこと"><a href="#%E6%AC%A1%E5%9B%9E%E3%81%AB%E6%B4%BB%E3%81%8B%E3%81%9B%E3%81%9D%E3%81%86%E3%81%AA%E3%81%93%E3%81%A8">次回に活かせそうなこと</a></h2> <ul> <li>メンター役は業務上のメンター役と業務外のこと(社内の制度や社内でのコミュニケーションなど)を担当するメンター役などを分けたほうが良いかもしれない <ul> <li>メンターが1人だと業務の遂行に目が行きがちで、慣れない社内での過ごし方や社内手続きなどに関して疎かになりがち</li> <li>メンター役の負荷を分散するため</li> </ul></li> <li>任せる業務はある程度メンター役の社員が精通しているもののほうが良い <ul> <li>インターン生が詰まったときに的確にアドバイス出来るようにするため。</li> <li>とはいえ、Backlogとかをさせてもつまらないので業務の選定は熟慮しなければならない</li> </ul></li> <li>R&D的な業務にしたのは良かった。もし次回があるなら、お題を与えてProof Of Concept的なものをやってもらっても良いかもしれない。 <ul> <li>インターン生には挑戦的な業務をしてもらうのが良いと思う。インターンが会社のPRの一環だと捉えると、挑戦的な業務を行えるということは会社の良い面をアピールすることにもなるからだ。</li> <li>たとえ失敗したとしても、チームはその失敗から得た教訓を活かすことができるし、インターン生は趣味の範囲では経験できないような失敗をするという経験を得ることができる。</li> <li>余談だけど、許容される範囲で失敗をするというのは学習においてとても重要だ。なぜなら、成功した理由というものは説明が難しいことが多く、かならずしもパターン化できない。しかし、失敗した理由を説明することは(簡単ではないが)可能であり、理由がわかればパターン化できるからだ。よって、失敗したほうが成長機会に結びつけやすい。</li> </ul></li> </ul> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>インターンを受け入れたことで、より多くの学びを得ることができた。また、これまで手つかずだったChaos engineeringを導入するきっかけにもなったし、インターン生だけでなくチームにとっても実りのあるものに出来たと思う。とはいえ、まだまだ反省点は多いので、次回はちゃんと態勢を整えて、より良いインターンを提供できるようにしなければならない。</p> <p>おしまい。</p> shige tag:crieit.net,2005:PublicArticle/15246 2019-07-16T00:42:42+09:00 2019-07-16T00:43:48+09:00 https://crieit.net/posts/Go-time-LoadLocation Goのtime.LoadLocationには外部依存性がある <h1 id="Exective Summary"><a href="#Exective+Summary">Exective Summary</a></h1> <p>Goのtime.LoadLocationには外部依存性があるので、引数に空白(<code>""</code>)、<code>UTC</code>、<code>Local</code>(<code>time.Local</code>にタイムゾーンを設定している場合)のいずれかではない場合(例えば<code>Asia/Tokyo</code>)は <code>unknown time zone</code>というエラーがでる場合がある。対策としては<code>time.Local = time.FixedZone("Local", 9*60*60)</code>としてから<code>time.LoadLocation("Local")</code>とすれば良い。</p> <h1 id="本編"><a href="#%E6%9C%AC%E7%B7%A8">本編</a></h1> <p>Goでは<code>time.LoadLocation</code>を利用してタイムゾーンの情報を得ることができる。得たタイムゾーンの情報は、例えば<code>time.ParseInLocation</code>という関数で使うことができる。一連の使い方の例として、<a target="_blank" rel="nofollow noopener" href="https://golang.org/pkg/time/#ParseInLocation">公式ドキュメント</a>では以下のような例が示されている。</p> <pre><code class="golang">package main import ( "fmt" "time" ) func main() { loc, _ := time.LoadLocation("Europe/Berlin") const longForm = "Jan 2, 2006 at 3:04pm (MST)" t, _ := time.ParseInLocation(longForm, "Jul 9, 2012 at 5:02am (CEST)", loc) fmt.Println(t) // Note: without explicit zone, returns time in given location. const shortForm = "2006-Jan-02T03" t, _ = time.ParseInLocation(shortForm, "2012-Jul-09T01", loc) fmt.Println(t) } </code></pre> <p>例では<code>Europe/Berlin</code>がタイムゾーンとして設定されているが、これは実行環境にある<a target="_blank" rel="nofollow noopener" href="https://github.com/golang/go/blob/eef0140137bae3bc059f598843b8777f9223fac8/src/time/zoneinfo_unix.go#L19-L26">タイムゾーンの設定ファイル</a>などを参照するために使われている。よって、Alpineのように何も環境下で<code>time.ParseInLocation</code>を実行して迂闊にタイムゾーンの名前を渡すと、 <code>unknown time zone</code>というエラーが発生してアプリケーションが実行できなくなる。そんな場合は<code>time.Local = time.FixedZone("Local", 9*60*60)</code>のように予め<code>time.Local</code>にタイムゾーンの設定情報を入れておいて<code>time.LoadLocation("Local")</code>とすることでエラーを防ぎ、かつアプリケーションから外部依存性を取り除くことが出来る。</p> <pre><code class="golang">// https://play.golang.org/p/lRiZxwT1u-U package main import ( "fmt" "time" ) func main() { time.Local = time.FixedZone("Local", 9*60*60) jst, _ := time.LoadLocation("Local") layout := "2006-01-02T15:04:05" t, _ := time.ParseInLocation(layout, "2019-07-15T20:21:22", jst) fmt.Println(t) // Print "2019-07-15 20:21:22 +0900 Local" } </code></pre> <p>もう少し踏み込んで確認したい場合は<a target="_blank" rel="nofollow noopener" href="https://github.com/golang/go/blob/9e277f7d554455e16ba3762541c53e9bfc1d8188/src/time/zoneinfo.go#L263-L308">ソースコード</a>を読んでみると良い。</p> <p>おしまい。</p> shige tag:crieit.net,2005:PublicArticle/14942 2019-04-21T23:35:17+09:00 2019-04-21T23:35:17+09:00 https://crieit.net/posts/tmrts-go-patterns-Builder-Pattern tmrts/go-patternsのBuilder Patternを日本語に勝手訳した <p>この記事は <a target="_blank" rel="nofollow noopener" href="https://github.com/tmrts/go-patterns/blob/master/creational/builder.md">https://github.com/tmrts/go-patterns/blob/master/creational/builder.md</a> を勝手に翻訳したものです。なので、内容の正確さは<strong>全く保証されてません</strong>。なので、気になる人はちゃんとオリジナルのコンテンツを読みましょう。(とはいえ、誤訳は指摘してもらえると嬉しい…</p> <h1 id="Builder Pattern"><a href="#Builder+Pattern">Builder Pattern</a></h1> <p>Builder patternとは、複雑なオブジェクトの表現から生成(コンストラクト)を分離させたものであるので、同じコンストラクトの処理で異なる表現を作成することができる。</p> <p>Goにおいては、通常、設定用のstructが同じふるまいを達成するために使われる。ただし、builder methodにstructを渡すということは<code>if cfg.Field != nil {...}</code>というチェックのボイラプレートのコードで満たすことができる。</p> <h2 id="実装"><a href="#%E5%AE%9F%E8%A3%85">実装</a></h2> <pre><code class="go">package car type Speed float64 const ( MPH Speed = 1 KPH = 1.60934 ) type Color string const ( BlueColor Color = "blue" GreenColor = "green" RedColor = "red" ) type Wheels string const ( SportsWheels Wheels = "sports" SteelWheels = "steel" ) type Builder interface { Color(Color) Builder Wheels(Wheels) Builder TopSpeed(Speed) Builder Build() Interface } type Interface interface { Drive() error Stop() error } </code></pre> <h2 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h2> <pre><code class="go">assembly := car.NewBuilder().Paint(car.RedColor) familyCar := assembly.Wheels(car.SportsWheels).TopSpeed(50 * car.MPH).Build() familyCar.Drive() sportsCar := assembly.Wheels(car.SteelWheels).TopSpeed(150 * car.MPH).Build() sportsCar.Drive() </code></pre> <h2 id="所感"><a href="#%E6%89%80%E6%84%9F">所感</a></h2> <p>(これは訳者、つまりこの記事を書いている人の勝手な意見)<br /> 同じコンストラクタを使って、パラメータに応じて異なる動作をする何かを生成するときに使えそう。例えば、異なるDBドライバを利用する何かだったり、コンテナのランタイムに応じて内部的な動作を切り替えるドライバ的な何かとか?サンプルコードの車の例はわかりやすくて理解に役立った。</p> shige tag:crieit.net,2005:PublicArticle/14929 2019-04-17T21:13:31+09:00 2019-04-17T21:15:30+09:00 https://crieit.net/posts/Python-Enum PythonのEnumのvalueの設定にはauto()をつかい、検索にはEnumのnameを使うのが良い <p>Python3.4ぐらいから導入されて、3.6ぐらいからまともに使えるようになった<code>Enum</code>とかいうのがある。<br /> * <a target="_blank" rel="nofollow noopener" href="https://docs.python.org/ja/3/library/enum.html">https://docs.python.org/ja/3/library/enum.html</a></p> <p>Enumが便利なのは、その名の通り、明示的に値を列挙するので、利用可能な値を確実に表現することができる。例えば、利用可能な値として色を表現するときには以下のようにできる。</p> <pre><code class="python">from enum import Enum class Color(Enum): red = "red" blue = "blue" green = "green" </code></pre> <p>このように定義すれば、</p> <pre><code class="python">>>> print(Color.red) Color.red </code></pre> <p>と記述できるので、コードの表現力が高くなるし、通常、ただの定数などで"red"と書く場合に比べて圧倒的にタイポしにくい(IDEなどによる補完が期待できるので)。</p> <p>で、ある値が上のように定義した<code>Color</code>の中に含まれているかを探したいときがある。その場合、<strong>値はEnumの値ではなく名前を使って検索したほうが良い</strong>。この辺の感覚、普段からちゃんと(?)Enumを使っていればわかるんだけど、Enumはドキュメントにも書かれているように</p> <blockquote> <p>列挙型は、一意の定数値に束縛された識別名 (メンバー) の集合</p> </blockquote> <p>なので、確実に一意が約束されているのは値ではなく名前のほう。なので、以下のように定義することができるため、</p> <pre><code class="python">from enum import Enum class Color(Enum): red = "ao" blue = "ao" green = "midori" </code></pre> <p>以下のような事故が起きる可能性がある。</p> <pre><code class="python">>>> # Color.blueを探したい >>> for c in Color: >>> if c.value == "ao": >>> print(c) Color.red </code></pre> <p>また、Enumの値は重複させることができるので、そもそも値で検索しても一意が保証されない。よって、Enumを利用するときはちゃんと名前を利用すること。そうすれば、ある値がEnumの中で定義されているかどうかは簡単に見つけることができる。</p> <pre><code class="python">>>> # ちゃんとnameで検索すること >>> "blue" in Color.__members__ True </code></pre> <p>ということで、値を設定しない場合は<code>enum.auto()</code>が使えるので、コーディング規約として、特別な理由がない限りはEnumに値を設定しないことにしたほうが良さそう。</p> <pre><code class="python">from enum import Enum, auto class Color(Enum): red = auto() # 上から連番が振られる blue = auto() gree = auto() </code></pre> shige tag:crieit.net,2005:PublicArticle/14922 2019-04-12T23:52:08+09:00 2019-04-13T09:28:16+09:00 https://crieit.net/posts/43f728e8f7dd291fac74e6d34aa15409 障害が起きたときに誰が謝るのかという問題(社内サービス編) <p>今は社内の開発者向けにサービス開発をしていて、まぁまぁの人数に使ってもらっている。で、サービスはまだまだ発展途上なので、リリースしてからもたまにシステムメンテナンスなどを行うことがある。まぁそんな感じでちょいちょい本番系をイジることがあるわけだけど、今日はそのメンテナンスをきっかけに障害が発生した。</p> <p>ここで問題なのは障害が発生したことではなくて(もちろん障害それ自体は問題なのだけど)、障害をどうハンドリングするかっていうこと。ここで言うハンドリングというのは、</p> <ol> <li>障害を調査し、解決すること</li> <li>障害を拡大させないために作業を指示すること</li> <li>障害の内容を利用者に説明すること</li> </ol> <p>通常、これらをすべてうまくこなせるのは障害を起こした本人だったりする(もちろんそれなりに技術や知識があることを必要とする)。であるならば、障害を起こした人はどの作業に専念すべきだろうか?これは当然#1である。次に#2で、#3。ということで、対応の優先度は上から順番に行うべき。</p> <p>とはいえ、実際のところ、それなりにいるユーザーたちからは苦情が上がってくるわけで、障害内容を説明しなければならない。で、この説明を考えるのはそれなりに難しい。もちろん嘘をつくのはダメだけど、とはいえ、どこまで内容を詳らかにするか。そして、正確に問題を説明できるのも障害を起こした本人。そして、なぜ#2を行わなければならないのかを説明できるのも障害を起こした本人。</p> <p>こんな感じであらゆることが障害を起こした本人にのしかかってくる。ここでようやく本題に入るわけなんだけど、一番やりたくない(というか、やる必要性が無い)し、面倒なのは、問題を説明し謝ることじゃないかなと思う。それに、当事者が謝るべきという考え方だと失敗を責める文化ができてしまって挑戦的な試みができなくなる組織になる。</p> <p>で、じゃあ他に誰が謝るのかということになる。謝るためには事態を正確に把握し、理解する必要があるけど、実際には無理。解決しようとする人を捕まえて、理解できるまで逐一説明させるとか自己満足でしかない。なので、(良い意味で)ごまかしが出来るセンスが必要になる。自分にはそういった感じのセンスがあるかなと思ってたけど、結局最後には障害を起こした本人に謝らせることになってしまった。まぁそれは本人が自主的にそうしたから、そうなったんだけど最後までちゃんと謝ることができなくて、ふがいないなーと思ったっていう、ただそれだけのために1000文字ぐらいをここに書いた。</p> shige tag:crieit.net,2005:PublicArticle/14917 2019-04-11T00:23:13+09:00 2020-03-31T13:33:34+09:00 https://crieit.net/posts/docker-Graceful-reload dockerをGracefulにreloadできるかどうか <h2 id="TL;DR"><a href="#TL%3BDR">TL;DR</a></h2> <ul> <li>出来る。</li> <li>けど、reloadで反映される設定は限られているので、<a target="_blank" rel="nofollow noopener" href="https://docs.docker.com/v17.09/engine/reference/commandline/dockerd/#miscellaneous-options">https://docs.docker.com/v17.09/engine/reference/commandline/dockerd/#miscellaneous-options</a>をよく読んで検証すること。</li> </ul> <h2 id="Main"><a href="#Main">Main</a></h2> <p>最近知ったんだけど、dockerには<code>log-opt</code>というオプションがあり、これを使うことで簡易なログローテートを行うことができる。<br /> - <a target="_blank" rel="nofollow noopener" href="https://docs.docker.com/config/containers/logging/configure/#configure-the-default-logging-driver">https://docs.docker.com/config/containers/logging/configure/#configure-the-default-logging-driver</a></p> <p>で、これを適用する必要があるんだけど、そういうときというのは大抵サービスが稼働中であり、サービスを止めずに設定を適用したい。例えばこれがNginxならreloadで(大抵の場合は)安全に設定を読み込ませることができるけど、それがdockerでも可能なのかどうか、ということを確認したい。</p> <p>対象のdockerはCentOS7の上で動いているので、まずはUnitファイルを見る。</p> <pre><code>[Unit] Description=Docker Application Container Engine Documentation=https://docs.docker.com ... [Service] Type=notify # the default is not to use systemd for cgroups because the delegate issues still # exists and systemd currently does not support the cgroup feature set required # for containers run by docker ExecStart=/usr/bin/dockerd -H unix:// ExecReload=/bin/kill -s HUP $MAINPID TimeoutSec=0 ... [Install] WantedBy=multi-user.target </code></pre> <p><code>ExecReload</code>が設定されているのでとりあえず<code>systemctl reload docker.service</code>というコマンドを実行することはできそう。じゃあ次は、実際にコンテナが動作している最中に実行したらどうなるかを見てみる。</p> <p>それっぽい状況を作るためにまず、Nginxのコンテナを立ち上げて外部から毎秒アクセスする。もう1つ適当なコンテナを立ち上げてコンテナ内部で毎秒<code>date</code>コマンドを実行する。なお、どちらもフォアグラウンドで動かしている。もしdockerがgracefulに設定を読み込むことができるのならば、各コンテナは正常に動作し続けるはず。</p> <p>まずはreloadする前の状態。</p> <pre><code>$ sudo systemctl status docker.service ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled) Active: active (running) since Wed 2018-12-05 14:55:37 JST; 4 months 4 days ago Docs: https://docs.docker.com Process: 27038 ExecReload=/bin/kill -s HUP $MAINPID (code=exited, status=0/SUCCESS) Main PID: 2432 (dockerd) Tasks: 57 Memory: 632.9M CGroup: /system.slice/docker.service ├─ 2432 /usr/bin/dockerd -H unix:// ├─ 2451 containerd --config /var/run/docker/containerd/containerd.toml... ├─23228 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8... ├─23235 containerd-shim -namespace moby -workdir /var/lib/docker/conta... └─26843 containerd-shim -namespace moby -workdir /var/lib/docker/conta... Apr 10 23:26:23 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:26:51 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:26:51 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:27:09 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:38:09 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:38:27 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:38:27 localhost dockerd[2432]: time="2019-04-10... Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable. Hint: Some lines were ellipsized, use -l to show in full. </code></pre> <p>reloadした後。</p> <pre><code>$ sudo systemctl status docker.service ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled) Active: active (running) since Wed 2018-12-05 14:55:37 JST; 4 months 4 days ago Docs: https://docs.docker.com Process: 30027 ExecReload=/bin/kill -s HUP $MAINPID (code=exited, status=0/SUCCESS) Main PID: 2432 (dockerd) Tasks: 57 Memory: 632.9M CGroup: /system.slice/docker.service ├─ 2432 /usr/bin/dockerd -H unix:// ├─ 2451 containerd --config /var/run/docker/containerd/containerd.toml... ├─23228 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8... ├─23235 containerd-shim -namespace moby -workdir /var/lib/docker/conta... └─26843 containerd-shim -namespace moby -workdir /var/lib/docker/conta... Apr 10 23:26:23 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:26:51 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:26:51 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:27:09 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:38:09 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:38:27 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:38:27 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:44:51 localhost dockerd[2432]: time="2019-04-10... Apr 10 23:44:51 localhost dockerd[2432]: time="2019-04-10... Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable. Hint: Some lines were ellipsized, use -l to show in full. </code></pre> <p>…とくにプロセス番号などは変わっていないし、動作中のコンテナは稼働し続けていて特に問題はなさそうだった。なので、reloadコマンドでプロセスが落ちる心配はしなくて良さそう。が、設定は反映されなかった。調べてみると、reloadで反映される設定は限定されており、<code>log-opts</code>は含まれていなかった…</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.docker.com/v17.09/engine/reference/commandline/dockerd/#miscellaneous-options">https://docs.docker.com/v17.09/engine/reference/commandline/dockerd/#miscellaneous-options</a>の"CONFIGURATION RELOAD BEHAVIOR"</li> </ul> <p>うーむ…というわけで、<code>log-opts</code>の設定を変更するにはrestartするしかない。めんどいなーとは思うが仕方ないね。</p> <p>ちなみに、dockerには<a target="_blank" rel="nofollow noopener" href="https://docs.docker.com/v17.09/engine/admin/live-restore/">live-restore</a>という機能があるので、これを使えばダウンタイムをなるべく減らすことができる。ただし、起動に時間がかかるコンテナ、例えばMySQLみたいやつ、だとこれは悪い方向に働く可能性があるので、注意して使う必要がある。</p> shige