Asakusa.rbとは私にとってどんな場所なのか

Asakusaの方面から来ました

今回、RubyKaigiに来た人は何度かこの画像を目にしたかもしれません。

f:id:joker1007:20180606213950j:plain

昔からAsakusa.rbに良く参加している人が発表資料を作る時に、良く使われている画像です。 いかにもシンボリックでアングルが良い写真なので、これが定番として使われています。私とモリスさんも今回のRubyKaigiで使わせてもらいました。

最近のAsakusa.rbに良く参加していて、今回のRubyKaigiに登壇していたメンバーは私が覚えている限りで以下の通り。(id表記 敬称略)

  • @tagomoris & me
  • @hsbt
  • @koic
  • @spikeolaf
  • @youchan
  • @aycabta
  • LT Speakers
    • @tad
    • @284km
    • @youchan

全員の資料を見てないので、全ての発表でこの画像が出てたかは分からないですが、めっちゃ多いですね。1日二人以上のペース。(抜けてたらマジでごめんなさい)

(2018/6/7 1:26 追記) 正確にはAsakusa.rbは通常のmeetupに一度でも参加したことがある人がメンバーであり、その定義から行くとAsakusa.rbと関係が無いトークの方が少ないレベルですね。また、参加したことがあるメンバーの居住地がAsakusaと呼ばれるというローカルルールがあります。そのため、世界各国にAsakusaがあるのですw (浅草のAsakusaに住んでいる俺)

そんなこんなで、Asakusa.rbというコミュニティの存在感がかなり示された結果、懇親会で何度も質問を受けることになりました。「Asakusa.rbって普段どういうことしてるんですか?」と。 後、Asakusa.rbがヤバイハッカー集団っぽくて怖いという話も良く聞きますね。

直接質問くれた人には色々と自分が思っていることを話しましたが、なんか需要ありそうだしせっかくのタイミングなので私にとってのAsakusa.rbをブログに書いておくことにしました。

Asakusa.rbはmeetup

Asakusa.rbは毎週火曜日に、大体神田から上野辺りの範囲で会議室やコミュニティスペースを借りて開かれているmeetupです。 ググるesaの公開ページが出てくるので、そこにメンバー登録して、毎週のmeetupの開催案内に「参加します」とコメントすれば、後は当日にフラっと行くだけです。 仕事が忙しい時は参加できなかったり、体調不良だったりする時もあるので、私も常に毎週参加している訳ではないですが、良く参加するメンバーが居たり、たまに参加するメンバーが居たり、色々です。

基本的には場所と時間のお知らせだけ書いてあり、テーマはありません。 各自、何かやることや話したいこと、相談したいことを適当に持ち込みます。コメントに「今日はgemのメンテやります」とか「資料作成やる予定です」とか書くことが多い。 とは言え、別に何も無くてもブラっと参加して良い会です。毎週時間になったら集まれる場所がある、というのがAsakusa.rbです。

meetupとは

Asakusa.rbはいわゆる勉強会ではありません。勉強する時間を確保するための場所として使っても全然OKだと思いますが、Rubyに関わることを中心にして人と人が会って話をする場所がAsakusa.rbです。

持ち込んだことや最近コード書いてて思ったこと等をベースに、雑談するのがメインと言っても良いでしょう。

初めてAsakusaに来る場合は、相談事があると一番入っていきやすいんじゃないかなと思ってます。皆色々教えてくれますし、これ以上ないぐらいに詳しい人から話を聞くチャンスがあります。 最近、仕事でこういう問題があったからgem作ってみたんだけどどうですかねーとか、こういう時どうやってデバッグしてるんですかーとか、そういう話があると「オッ」ってなった人が教えてくれたり、それは分からんなー(分からんもんは分からんw)ってなったりします。

他の人同士が話している内容でも、自分が興味ありそうなら首を突っ込んで話に混じっていくのが一番楽しめると思います。そういう事を切っ掛けに普段からある疑問なり困り事を質問するのも良さそうです。

とは言え、毎週技術の話ばかりをしている訳ではなく、どこかの美味い食事処の話やゲームの話、ガジェットの話をしてたりする時間も良くあります。

Asakusa.rbと飲み会

最近のAsakusa.rbには、主催の松田さんを筆頭にお酒と美味しいご飯が好きなメンバーが大勢居ます。なので終わった後に飲んで帰りますかーという日もしばしばあります。(毎回ではない) ただ結構meetupの終わりが遅いので、22:30ぐらいから飲みに行ったりすることがあります。 Asakusa.rbは地域コミュニティなので近所に住んでる人が多く、良い店にあたると終電という概念が存在しなくなるので、そちらにも参加する場合はタイムマネジメントが重要です。他の人の終電を気にする人は余り居ないと思われますw

こんな感じでお酒を飲んでワイワイ喋るのが好きな人も多いです。

RubyKaigiとAsakusa.rb

何故、RubyKaigiでこんなにAsakusa.rbメンバーの登壇者が多いのかというと、メンバーに高いレベルでコードと向き合ってる人が多いというのもあると思いますが、大きな要因は毎週松田さんと会う機会があるという点にあると私は思っています。

毎週、松田さんがRubyKaigiのために奔走している姿をチラチラ目にしている中で、CFP出して欲しいという話を毎度聞くことになる訳で、こりゃ是非とも出してKaigiを盛り上げる力になりたいと思う訳です。更にRubyKaigiがどういう思いを持って運営されているか、どういう発表内容が望まれているかという点について、質問したり聞き易い立場に居ます。 なので、技術力という点を抜きにしても、RubyKaigiを楽しんでいく上で恵まれたコミュニティであり、そういう部分が表われているのかなあと思います。

(ただ、RubyKaigiのトーク審査は鬼厳しいので、それだけで通る程甘くはない……。)

Asakusa.rbは怖い?

現時点の私としては全く怖くなくて、ただ単に楽しい集まりなのですが、かつての自分を思い返すと、そういう気持ちも分からなくはないという感じです。 私が初めてAsakusa.rbに行ったのはもう4年から5年前ぐらい(良く覚えてない)だと思いますが、初めての頃はちょっとビビりながら参加していたと思います。 その感覚をより正確に言うなら「畏れ多い」とかでしょうか。端的に表現すると「怖い」ってなるのは分からなくはない。

ただ、実際の所、Rubyが好きな人が集まってワイワイ雑談してるのがメインの会であり、本当に怖いことなんか特に何も無い訳です。バリバリコードレビューしている訳でもなく、全員が常にRubyをハックしている訳でもないです。私も仕事が終わってなくて普通に仕事しながら回りの雑談を聞いている、みたいな時がよくあります。

もし、コードを書いている様に見せかけたり、言い方が悪いかもしれませんが日々の活動がハッタリっぽい人だと話に入っていきづらいかもしれませんが。そういう振舞いは嫌いな人が多いと思います。

何だかんだでコミュニティって、継続的に参加しているメンバーがいて、ある程度話がしやすい関係性というものが既に構築されている場所です。新しいメンバーを歓迎する工夫があったとしても、やっぱり慣れない集団に参加していこうとするのは勇気が必要な行為だと思います。(特に我々はコミュ力に難のある側の人間なので……)

Asakusa.rbに限らず、何度も参加して入れそうな話題に首を突っ込んで、ちょっとづつ慣れていくというのはどこのコミュニティでも必要なことじゃないかと思います。ただ、皆Rubyが好きでコードを書くのが好きでそれに関する話をするのも好きだ、という前提があるのでそこを取っ掛かりに話をしやすいのは、どこのRubyコミュニティでも共通のことだと思います。Asakusa.rbももちろんそうです。

Asakusa.rbでは新しいメンバーが来ると、全員自己紹介して普段何をしているのかとか今日はこういう話がしたくて来た、という時間を取っています。そこを切っ掛けに声をかけにいくのも良いと思います。 しかし、その時間は松田さんが来たタイミングで始まるので、たまにその時間がえらく遅くなる時があるのがハードルの一つかもしれないw

後、飲み会が不定期でしかも時間がかなり遅いので、そこから交流するのもハードルが高いかもしれない……。

Asakusa.rbと海外からのゲスト

松田さんを始め、海外カンファレンスで登壇経験があるメンバーも多いので、たまに海外から日本に遊びに来た人がmeetupに参加してくれることがあります。そういう時は常に英語って訳ではないですが、英語比率が上がりますし、自己紹介も英語になります。(私は余り上手くないです……)

英語が得意でない場合は、初参加のタイミングとしては、ちょっとハードルがあるかもしれません。ただRubyKaigiで多少なりとも海外から来たRubyistと交流をするための練習としては良い経験が得られると思います。私もじわじわ慣れてきたのかなーという感じです。毎年のRubyKaigiでちょっとづつ海外から来た方と話をする時間を増やすことが出来ているのはAsakusa.rbのおかげもあると思います。

ちなみに、Asakusa.rbに海外ゲストが来る時は交流の比率を上げるために、早めの時間から飲み会に移行することが多いですね。

Wellcome Asakusa.rb

色々と書かせていただきましたが、これらは私が感じているAsakusa.rbです。これが正しいという訳でも無いですし、回によってイメージが変わることもあります。何度も参加していればきっと楽しくなるし得られるものもあると私は思っていますが、全ての人にとってそうでないこともあるでしょう。

私としてはとても楽しい会なので、これからも定期的に参加するつもりだし、新しく色々な人が参加してくれて刺激が得られるのも嬉しいことなので、RubyKaigiやこのエントリ等を通じて興味を持った人がいれば、是非気楽にAsakusa.rbに参加してみてください。もちろんAsakusa.rbに限らず、他にも多数存在する地域.rbにも興味があれば是非参加してみてください。それぞれ特色ある集まりです。楽しんで刺激が得られる空間が見つかるかもしれません。

やっぱり仲間が居ると楽しいものだし、ただでさえ最高に楽しいRubyKaigiがもっと楽しくなります。

Wellcome Ruby Community !!

俺史上最高のRubyKaigi 2018

仙台で行われたRubyKaigi 2018に参加してきました。 RubyKaigiは毎年最高のイベントなのですが、今年は総合的に今迄で最も良い体験ができたRubyKaigiでした。

実績解除

今回のRubyKaigiで初めてメインスピーカーとして登壇することができました。 私が初めてRubyKaigiに参加したのが、確か2011年で1stシーズンのFINALとして開催された時でした。 そして、その頃からずっとThe RubyKaigiのメインスピーカーというのは憧れの場であったのですが、7年の歳月を経て辿り着くことができたことを本当に嬉しく思っています。

f:id:joker1007:20180604172956j:plain

今回の発表内容は「Hijacking Ruby Syntax in Ruby」というタイトルで話をさせていただきました。

Asakusa.rbで雑談してたのが切っ掛けで、@tagomoris さんと盛り上がってRubyのダイナミックな機能を活用した様々なハックに手を出す機会があり、その内容を元に @tagomorisさんと二人で発表しました。 TracePointやRefinementsやBinding、そしてRubyに存在する様々なフックをこれでもかという程使った内容になっているので、それらの内容に興味がある人には是非資料を見ていただきたいですね。

会場の客足も良くて椅子が足りなくなるぐらいだったし、TLや後の懇親会での評判を聞く限りではかなり楽しんでもらえた様で、自分としても大成功だったと言って良いでしょう。 特に嬉しかったのが、Refinementsの作者であるShugo Maedaさんがブログで印象に残った発表として紹介してくれたことです。これはマジでめっちゃ嬉しい。

一緒に発表してくれた @tagomoris さんにも超感謝しております。ありがとうございました! & お疲れ様でした! (追記: モリスさんのエントリ)

その他のセッションについて

@maciejmensfeld さんのkarafkaについての発表は、普段の業務的にも気になる発表で、とても良い話でした。発表後の飲み会で質問が出来たのも良かった。 Karafka - Ruby Framework for Event Driven Architecture - RubyKaigi 2018

また、羽角さんのmruby/cについての発表は内容もさることながら、私としては憧れざるを得ないCOOLさがあって最高でした。RubyKaigiという場所で、私も大好きな日本酒である十旭日の話をするのはカッコ良過ぎですね。実際にmruby/cという新しい技術を使って旭日酒造さんとビジネス事例を生み出しているという点でも凄い話だと思います。(29BYはRubyが酒造に活用された十旭日らしいので、是が非でも飲まなければならない) Firmware programming with mruby/c - RubyKaigi 2018

後、金子さんのASTにカラム情報を入れて正確な位置情報を取れる様にするという仕事は可能性が色々広がりそうで、聞いていて楽しかった。 RNode with code positions - RubyKaigi 2018

仙台という場所

魚介メインで大分美味しいものが食べられた。ホヤ、牡蠣、穴子、どれも美味かった。 そしてお酒も美味い。日本の地方都市開催は多少お金はかかるかもしれないが、めっちゃ楽しめるので素晴らしいと思う。

f:id:joker1007:20180531213342j:plain f:id:joker1007:20180531213107j:plain f:id:joker1007:20180602232539j:plain f:id:joker1007:20180602224943j:plain

ジョジョ

仙台、つまりS市 杜王町ジョジョを愛して止まない自分としては、この町はRubyKaigiの場所として最高だった。 浮かれて3日間、ジョジョTシャツを毎日着てたぐらいです。ちなみに上着はSPW財団のジャケットを着てました。

f:id:joker1007:20180603123817j:plain

ちゃんと、吉良吉影の領収書も貰った!

RubyKaigi 2018 IN 仙台

RubyKaigiそのものの経験としても、初スピーカー実績解除できて最高だったし、仙台観光もできて美味しいものも食べれて最高だったし、むかでやで領収書も貰えて最高だった。 つまり、最高だった訳です。(語彙)

ありがとうRubyKaigi。来年も楽しみにしてます。

f:id:joker1007:20180604183426j:plain f:id:joker1007:20180604183415j:plain

Professional Rails on ECS (rails developer meetup 2017)

このエントリはRails developer meetup 2017で発表した内容をブログとして書き出したものです。 サンプルのスニペットが多いので資料の代わりにエントリとして公開します。 スライド用のmarkdownを元に起こしたものなので、少し読み辛いかもしれませんがご容赦ください。

ECSとは

  • Dockerコンテナを稼動するためのクラスタを管理してくれるサービス
  • 使えるリソースを計測し、自動でコンテナの配置先をコントロールしてくれる
  • kubernetesではない。最近、kubernetesが覇権取った感があって割と辛い
  • 今はEC2が割とバックエンドに透けて見えるのだが、Fargateに超期待
  • ECS or EKS :tired_face:

RailsアプリのDockerize

オススメの構成

  • 実際にデプロイするimageは一つにする
    • 例えばstagingやproduction等のデプロイ環境の違いはイメージでは意識しない
  • 手元で開発する様のDockerfileは分ける

FROM ruby:2.4.2

ENV DOCKER 1

# install os package
RUN <package install>

# install yarn package
WORKDIR /yarn
COPY package.json yarn.lock /yarn/
RUN yarn install --prod --pure-lockfile && yarn cache clean

# install gems

WORKDIR /app
COPY Gemfile Gemfile.lock /app/
RUN bundle install -j3 --retry 6 --without test development --no-cache --deployment

# Rails app directory
WORKDIR /app
COPY . /app
RUN ln -sf /yarn/node_modules /app/node_modules && \
  mkdir -p vendor/assets tmp/pids tmp/sockets tmp/sessions && \
  cp config/unicorn.rb.production config/unicorn.rb

ENTRYPOINT [ \
  "prehook", "ruby -v", "--", \
  "prehook", "ruby /app/docker/setup.rb", "--" ]

CMD ["bundle", "exec", "unicorn_rails", "-c", "config/unicorn.rb"]

ARG git_sha1 # どのコミットなのか中から分かる様にする

RUN echo "${git_sha1}" > revision.log
ENV GIT_SHA1 ${git_sha1}

docker build


assets:precompile

RailsのDocker化における鬼門の一つ

  • S3 or CDNを事前に整備しておくこと
  • ビルド時に解決するがビルド自体とは独立させる
  • docker buildした後で、docker runで実行する

  • ビルドサーバーのボリュームをマウントし、assets:precompileのキャッシュを永続化する
  • キャッシュファイルが残っていれば、高速にコンパイルが終わる
  • manifestをRAILS_ENV毎にrenameしてS3に保存しておく この時、コミットのSHA1を名前に含めておく。(build時にargで付与したもの)
docker run --rm \
  -e RAILS_ENV=<RAILS_ENV> -e RAILS_GROUPS=assets \
  -v build_dir/tmp:/app/tmp app_image_tag \
  rake \
    assets:precompile \
    assets:sync \
    assets:manifest_upload

prehook

ENTRYPOINTで強制的に実行する処理で環境毎の差異を吸収する

  • ERBで設定ファイル生成
  • 秘匿値の準備
  • assets manifestの準備
    • さっきRAILS_ENV毎に名前付けてuploadしてたのをDLしてくる

秘匿値の扱い

  • 設定ファイル自体を暗号化してイメージに突っ込む
    • 環境変数で直接突っ込むとECSのconsoleに露出する
    • 値の種類が多いと環境変数管理する場所が必要になる
  • コンテナ起動時に起動環境の権限で複合化できると良い
    • prehookで複合化処理を行う

yaml_vault

https://github.com/joker1007/yaml_vault

  • Rails5で入った、encrypted secrets.ymlの拡張版
  • AWS-KMS, GCP-KMSに対応している
  • KMSを利用すると秘匿値にアクセスできる権限をIAMで管理できる
  • クラスタに所属しているノードのIAM Roleで複合化
  • 設定をファイルに一元化しつつ安全に管理できる
  • Railsの場合、secrets.ymlをメモリ上で複合化して起動できる
    • ファイルに展開後の値が残らない

開発環境

docker-composeとディレクトリマウントで工夫する


version: "2"
services:
  datastore:
    image: busybox
    volumes:
      - mysql-data:/var/lib/mysql
      - vendor_bundle:/app/vendor/bundle
      - bundle:/app/.bundle

  app:
    build:
      context: .
      dockerfile: Dockerfile-dev
    environment:
      MYSQL_USERNAME: root
      MYSQL_PASSWORD: password
      MYSQL_HOST: mysql
    depends_on:
      - mysql
    volumes:
      - .:/app
    volumes_from:
      - datastore
    tmpfs:
      /app/tmp/pids


  mysql:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    volumes_from:
      - datastore

Macの場合

  • ちなみにボリュームマウントが死ぬ程遅いので、何らかの工夫が必要
  • dinghyかdocker-syncで頑張る
    • どっちも辛い
  • Mac捨てるのがオススメ

俺の開発スタイル

  • 開発用Dockerfileでzshや各種コマンドを入れておく
  • docker-compose run --service-ports app zsh
  • シェルスクリプトで自分の.zshrcやpeco等をコピーしdocker exec zsh
  • ファイルの編集だけはホストマシンで行い、後は基本的にコンテナ内で操作する

set -e

container_name=$1

cp_to_container()
{
  if ! docker exec ${container_name} test -e $2; then
    docker cp -L $1 ${container_name}:$2
  fi
}

cp_to_container ~/.zshrc /root/.zshrc
if ! docker exec ${container_name} test -e /usr/bin/peco; then
  docker exec ${container_name} sh -c "curl -L -o /root/peco.tar.gz https://github.com/peco/peco/releases/download/v0.4.5/peco_linux_amd64.tar.gz && tar xf /root/peco.tar.gz -C /root && cp /root/peco_linux_amd64/peco /usr/bin/peco"
fi

docker exec -it ${container_name} sh -c "export TERM=${TERM}; exec zsh"

デプロイの前に

ECSの概念について


TaskDefinition

  • 1つ以上のコンテナ起動定義のセット
    • イメージ、CPUのメモリ使用量、ポート、ボリューム等
    • 物理的に同じノードで動作する
  • docker-composeの設定一式みたいなもの
  • kubernetesでいうPod

Task

  • TaskDefinitionから起動されたコンテナ群
  • 同一のTaskDefinitionから複数起動される

Service

  • Taskをどのクラスタでいくつ起動するかを定義する
  • ECSが自動でその数になるまで、コンテナを立てたり殺したりする
  • コンテナの起動定義はTaskDefinitionを参照する
  • コンテナが起動したノードをALBと自動で紐付ける
  • kubernetesにも似た概念がある

ECSへのデプロイの基本

  1. TaskDefinitionを更新
  2. Serviceを更新
  3. 後はECSに任せる

ecs_deploy

https://github.com/reproio/ecs_deploy

  • capistrano plugin
  • TaskDefinitionとServiceの更新を行う
  • Service更新後デプロイ状況が収束するまで待機する
  • 更新したTaskDefinitionのrevisionを他のタスクで参照できる
  • TaskDefinitionやServiceの定義はRubyのHashで行う
    • Hash化できれば何でも良いので、YAMLでもJSONでも

Why use Capistrano

  • 既存の資産が多数ある
    • slack通知のフックとか
  • デプロイのコマンドが変化しない
  • 設定ファイルの場所や定義も大きく変化しない

個人別ステージング環境

  • アプリサーバーだけで良いなら容易に実現可能
  • RDB等のデータストアを個別に持つなら色々難しい
  • 弊社はアプリサーバーだけ個別にデプロイ可能
  • データストアを弄る場合はフルセットの環境を使い、そこを占有する

インフラの準備

terraform等で以下のものを準備する

  • ALBを一つ用意する
  • 個人別のサブドメインをRoute53に定義
  • ALBのTarget Groupを個人別に定義
  • ALBのホストベースルーティングを定義

その後capistranoにmemberという環境を定義し、各メンバーが自分の名前でtarget_ groupやTaskDefinitionの名前を使ってデプロイ出来る様に諸々を変数化する


terraformの例

module "acm" {
  source = "../acm"
}

resource "aws_alb" "developers" {
  name = "developers"
  internal = false
  subnets = ["your-subnet-id"]
  security_groups = ["your-security-group-id"]
  idle_timeout = 120
}

resource "aws_alb_target_group" "per_developer" {
  count = "${length(var.users)}"

  name = "${element(var.users, count.index)}"
  port = 8080
  protocol = "HTTP"
  vpc_id = "your-vpc-id"
  deregistration_delay = 0

  health_check {
    path                = "/health_check"
    interval            = 45
    matcher             = 200
    unhealthy_threshold = 8
  }
}

resource "aws_alb_listener" "developers" {
  load_balancer_arn = "${aws_alb.developers.arn}"
  port = 443
  protocol = "HTTPS"
  ssl_policy = "ELBSecurityPolicy-2016-08"
  certificate_arn = "your-certificate-arn"

  default_action {
    target_group_arn = "${element(aws_alb_target_group.per_developer.*.arn, 0)}"
    type = "forward"
  }
}

resource "aws_alb_listener_rule" "per_developer" {
  count = "${length(var.users)}"

  listener_arn = "${aws_alb_listener.developers.arn}"
  priority     = "${count.index + 1}"

  action {
    target_group_arn = "${element(aws_alb_target_group.per_developer.*.arn, count.index)}"
    type = "forward"
  }

  condition {
    field  = "host-header"
    values = ["${element(var.users, count.index)}-*.example.com"]
  }
}

resource "aws_route53_record" "per_developer_app" {
  count = "${length(var.users)}"

  zone_id = "${var.zone_id}"
  name = "${element(var.users, count.index)}-app.${var.domain_name}"
  type = "A"
  alias {
    name = "${aws_alb.developers.dns_name}"
    zone_id = "${aws_alb.developers.zone_id}"
    evaluate_target_health = true
  }
}

デプロイ定義の例

set :rails_env, :staging
set :branch, ENV["GIT_SHA1"] || (`git rev-parse HEAD`).strip
set :slackistrano, false

raise "require DEVELOPER env" unless ENV["DEVELOPER"]

target_group_arn = Aws::ElasticLoadBalancingV2::Client.new.describe_target_groups(names: [ENV["DEVELOPER"]]).target_groups[0].target_group_arn

set :ecs_services, [
  {
    cluster: "staging-per-member",
    name: "#{ENV["DEVELOPER"]}-#{fetch(:rails_env)}",
    task_definition_name: "#{ENV["DEVELOPER"]}-staging",
    desired_count: 1,
    deployment_configuration: {maximum_percent: 100, minimum_healthy_percent: 0},
    load_balancers: [
      target_group_arn: target_group_arn,
      container_port: 8080,
      container_name: "nginx",
    ],
  },
]

Autoscale (近い内に不要になる話)

Fargate Tokyoリージョンはよ!

  • 現時点でECS ServiceのスケールとEC2のスケールは独立している
  • Service増やしてもEC2のノードを増やさないとコンテナを立てるところがない
  • 増やすのは簡単だが減らす時の対象をコントロールできない

というわけでデフォルトで良い方法がない。


ecs_deploy/ecs_auto_scaler

https://github.com/reproio/ecs_deploy

  • CloudWatchをポーリングして自分でオートスケールする :cry:
  • Serviceの数を制御し、EC2の数はServiceの数に合わせて自動で収束させる
  • スケールインの際は、コンテナが動作していないノードを検出して落とす
  • コンテナが止まるまではEC2のノードは落とさない

polling_interval: 60

auto_scaling_groups:
  - name: ecs-cluster-nodes
    region: ap-northeast-1
    buffer: 1 # タスク数に対する余剰のインスタンス数

services:
  - name: app-production
    cluster: ecs-cluster
    region: ap-northeast-1
    auto_scaling_group_name: ecs-cluster-nodes
    step: 1
    idle_time: 240
    max_task_count: [10, 25]

    scheduled_min_task_count:
      - {from: "1:45", to: "4:30", count: 8}
    cooldown_time_for_reach_max: 600
    min_task_count: 0
    upscale_triggers:
      - alarm_name: "ECS [app-production] CPUUtilization"
        state: ALARM
    downscale_triggers:
      - alarm_name: "ECS [app-production] CPUUtilization (low)"
        state: OK

ecs_auto_scaler自体もコンテナに

  • ecs_auto_scalerはシンプルなforegroundプロセス
  • 簡単なDockerfileでコンテナ化可能
  • こいつ自身もECSにデプロイする

まあ、Fargateで不要になると思う


コマンド実行とログ収集

ECSにおいて特定のノードにログインするというのは負けである rails runnerやrakeをSSHで実行とかやるべきではない そのサーバーの運用管理をしなければならなくなる


wrapbox

https://github.com/reproio/wrapbox

  • ECS用のコマンドRunner
    • 半端に汎用性を持たせようとしたんでコードが微妙に……
  • TaskDefinitionを生成、登録し、即起動する
  • 終了までステータスをポーリングし待機する
  • タスク起動権限はIAMでクラスタ単位で管理できる
  • 慣れるとSSHとかデプロイが不要でむしろ楽

default:
  region: ap-northeast-1
  container_definition:
    image: "<ecr_url>/app:<%= ENV["GIT_SHA1"]&.chomp || ENV["RAILS_ENV"] %>"
    cpu: 704
    memory: 1408
    working_directory: /app
    entry_point: ["prehook", "ruby /app/docker/setup.rb", "--"]
    environment:
      - {name: "RAILS_ENV", value: "<%= ENV["RAILS_ENV"] %>"}

wrapboxで実行したコマンドログの取得

  • papertrailにログを転送し、別スレッドでポーリングしてコンソールに流すことができる
  • 原理的に他のログ集約サービスでも実現可能だが、現在papertrailしか実装はない

default:
  # 省略
  log_fetcher:
    type: papertrail # Use PAPERTRAIL_API_TOKEN env for Authentication
    group: "<%= ENV["RAILS_ENV"] %>"
    query: wrapbox-default
  log_configuration:
    log_driver: syslog
    options:
      syslog-address: "tcp+tls://<papertrail-entrypoint>"
      tag: wrapbox-default

db:migrate

capistranoのhookを利用しwrapboxで実行する

def execute_with_wrapbox(executions)
  executions.each do |execution|
    runner = Wrapbox::Runner::Ecs.new({
      cluster: execution[:cluster],
      region: execution[:region],
      task_definition: execution[:task_definition]
    })
    parameter = {
      environments: execution[:environment],
      task_role_arn: execution[:task_role_arn],
      timeout: execution[:timeout],
    }.compact
    runner.run_cmd(execution[:command], **parameter)
  end
end

desc "execution command on ECS with wrapbox gem (before deployment)"
task :before_deploy do
  execute_with_wrapbox(Array(fetch(:ecs_executions_before_deploy)))
end

set :ecs_executions_before_deploy, -> do
  # ecs_deployの結果からTaskDefを取得
  rake = fetch(:ecs_registered_tasks)["ap-northeast-1"]["rake"]
  raise "Not registered new task" unless rake

  [
    {
      cluster: "app",
      region: "ap-northeast-1",
      task_definition: {
        task_definition_name: "#{rake.family}:#{rake.revision}",
        main_container_name: "rake"
      },
      command: ["db:ridgepole:apply"],
      timeout: 600,
    }
  ]
end

db:migrate -> ridgepole

  • migrateのupとdownがめんどい
  • 特に開発者用ステージング
  • ridgepoleならデプロイ時に実行するだけで、ほぼ収束する
  • エラーが起きたらslackにログを出して、手動で直す
  • productionはdiffがあればリリースを停止させる
  • diffを見てリリース担当者が手動でDDLを発行する

テストとCI

以下を参照。 https://speakerdeck.com/joker1007/dockershi-dai-falsefen-san-rspechuan-jing-falsezuo-rifang


コンテナを真面目に運用するためには、結構色々考えることがある

何かしら参考になれば幸いです

如何にしてAsakusaから来た面々はISUCON7の予選に敗北したのか

とてもとても悲しいので、とりあえずやったことと言い訳を書いて気を紛らわせることにする。 敗北した身でグダグダ言うのが格好悪いことは百も承知だが、人間には魂の救済が必要であることをご理解いただきたい。

序盤〜方針決定

最初パスワードのコピペミス等でサーバーからガンガンBANされて、そもそもログインできなくなる。これで10分から20分ぐらい無駄にした気がする。 テザリングにIPを切り替えたり、他のノードから入ったりして、何とか公開鍵でログインできる環境を整える。

適当にベンチ流してスコアを取る前に、nginxのログ設定や構成を確認しalpを使って集計できる準備を整えた。デフォルト実装とRuby実装でベンチを流す。その裏で実装を一通り読む。

f:id:joker1007:20171023022330j:plain

ざっくり図を書いて、相談。とにかく/iconsを何とかしないと話が進まないので、静的ファイルとして書き出してCache-Controlだよね、までは即決。

モリスさんの発案により全てのノードにPOSTが来た時点でPUTリクエストを転送し、WebDAVで全てのノードにファイルを書き出して、後はnginxでサーブすればいいという方針で行こうということになる。この決定は後になって思うと危険な判断ではあった。

そこに多少の準備がかかるので、その間にSQLやアプリケーションの実装周りで明らかに非効率な所を潰すことにした。俺はRubyコードの全般を直し、やんちゃさんにDB周りのインデックスやSQLを見てもらった。

中盤

/iconsの静的ファイル化が完了し、nginxにより直接サーブが可能になる。この時点でスコアが3万前後。画像ファイルのキャッシュとかは一切してないので、周りのスコアとかを見ても、まあこんなもんだろうと思う。

ここで一つ目の壁にぶち当たる。nginxにexpiresを追加してCache-Controlが動作する様にしたのだが、スコアが伸びない。この時点でベンチマーカーが一体何であるのかを考えることになる。

ここで我々のチームは、ベンチマーカーがCDNかキャッシュプロキシの様なものからのリクエストである、ということをすぐに想定できなかった。少なくとも自分はブラウザかAPIのクライアントの様なものしかすぐに想定できなかった。

ただ、ネットワークがサチっていることはメトリックから明らかであり、周りのチームとのスコア差を考えると、304かCache-Controlによるキャッシュしか方法が無いだろうというのは分かっていた。

その間に裏でワーカーの数やらキャッシュを調整し、スコアを45000ぐらいまで伸ばす。やったのは大体以下の様な感じ。

  • チャンネルは作ったら変更されないので、起動時に全部Redisのハッシュにストア
  • チャンネルが保持しているメッセージ数もRedisにハッシュでストアしメッセージが入ったらhincrbyする
  • db.prepareを全てmysql2-cs-bindのxqueryに置き換える
  • RedisとMySQLのコネクションをコネクションプール化し起動時に接続済みにしておく
  • nginxとpumaのプロセス数を2にして、スレッド数やwocker_connection数を調整
  • カーネルのsomaxconnが128で少なかったので数を増やす
  • SQLのクエリワークロードに合わせてインデックスを張る (そんなにやることはなかった)
  • N+1をJOINに変えて潰す

ちなみに、pubsubとか使えそうかなとは考えたが、3台でそれやってもなあというのと、短時間で実装するのが無理っぽかったので止めた。

しばらく悩んだ後にモリスさんが気付いてCache-Controlにpublicを突っ込んだらスコアが倍ぐらいになり、どうもCDNっぽいという話が出てきた。

終盤

Cache-Controlがある程度効いているのだが、やっぱりネットワークがサチってスコアが10万辺りで止まる。304を返して何とかする方法があるはずだ、と思ってパケットキャプチャをしてクライアントの動きを見たのだが、どうもIf-Modified-SinceやらIf-None-Matchが見当たらなかった。(本当は少数ログに残ってた可能性はあるが気付かなかった)

で、どうやって304で返すかで色々と迷走することになる。この時にベンチマークの仕様について確信が持てなかったため、肝心なことに気付けなかった。EtagやLast-Modifiedが入ってて、何故ネットワークが先にサチるのかが分からなかった。

その間も裏でアプリケーションのキャッシュ可能な場所を探したり、nginxのopen_file_cacheやらTCP周りのカーネルチューニングをやって11万ぐらいは安定して出る様になっていた。

しかし、最終的に最後まで304でレスポンスを返すことができず、アプリケーションやDBにかかっている負荷は5割に満たないまま時間切れとなった。

アプリケーションサーバー側に来ていたトラフィックは遅くても100msec、POSTでも200msecぐらいで、半数以上は数十msecで返せていたのでウェブアプリケーションとしては充分な水準だったとは思う。

最終的な敗因

終わった後の情報からの推測によると、一番致命的だったのは静的なファイルを返す様にした際に各ノードに初期画像を撒いたのだが、その時にファイルの更新時間を揃えるのを忘れたという点だと思われる。

ブラウザだったら、Cache-Controlで一旦ローカルにキャッシュされてしまえば、If-Modified-Sinceが多少異なっていても、そもそもサーバーにアクセスしないので、何も問題は無い。

しかし、後から推測するにベンチマーカーはCDNからのリクエストを想定しているものだと思われる。なのでレスポンスヘッダの状態をちゃんとチェックしてキャッシュの削除をしていたんじゃないだろうか。

もし、ファイルの更新時間が異なる同一ファイルが各ノードにあった場合、あるノードから戻ってきたLast-Modifiedが自分がリクエストしているIf-Modified-Sinceより前になる場合、というのが発生する。ここの値が一致しないとnginxはデフォルトでは普通にコンテンツを返すはず。

ここで、一つ気付いていながら確信が持てず試さなかったのが、nginxのif_modified_since beforeである。もしサーバーが持っているファイルの更新時間より未来のIf-Modified-Sinceが送られてきても、nginxは304を返すことができる設定がある。恐らくうちの場合はこれでほとんどのコンテンツに対して304を返すことができた可能性がある。

一方でEtagもレスポンスヘッダに含まれており、ファイルの更新時刻が異なる場合それも異なってしまうので、最終的にはnginxの実装に依る様な気もする。どっちにしても駄目だったかもしれないが、そこまでの知識はない。

何にせよ、上記の問題によって、初期画像に対するリクエストのほとんどが304を返すことができなかった。

そのためネットワークがサチって負荷が上がりきらずスコアが伸びなかった。3台分の帯域をフルに使ってWeb,DB共にリソースの半分以上の余力を残して11万点だったので、304がまともに返せていれば、20万後半から30万ぐらいは行けたんじゃないかとは思うのだが、完全に別の世界線の話になってしまったので、我々には本当のところは分からない……。

ちなみに、WebDAVでファイルを複製して各ノードに配置したことが危険だったのは、ファイルを書き込んだタイミングが1秒以上ズレると上記と同じ問題が発生するところだった。

そもそもファイルの更新時間という点に気付いていなかったので、ここも致命的だったのかどうかは定かではない。ローカルネットワークは帯域もレイテンシも全然余裕であり、書き込みが終わるまでに全体で0.1秒以下ぐらいしかかかってないので、よっぽど運が悪くない限りはそうそうズレることは無いと思っていたのだが、ぶっちゃけ各ノードの時計が秒単位で一致してたかというと、はなはだ怪しい。恐らくこれも影響があったのではないだろうか。

言い訳

実際、適当にいくつかの画像にアクセスした限りでは複数のノードでもEtagもLast-Modifiedも一致していたのも気付けなかった点の一つではある。

しかし、今回私にとってとてもハードルが高かったのは恐らくCDNというものを想定したベンチマーカーだったこと、そしてその挙動特性を知る経験が無かったことだ。

一応、数年Webに関する仕事をしてきて、今CTOなんぞをやっている訳だが、CDNが必要になったことなど今迄一度も無かった。ずっとB2Bで仕事をしていたのでPOSTの数が多いとかやたら複雑だとか一回のGETでめちゃめちゃ複雑な計算結果を返さなきゃいけないことは一杯あるのだが、GETはそんなに世界のあちこちから来ないし、1分に数件とか処理できれば余裕な仕事がほとんどだった。

というわけで、CDN等は遥か遠い世界の話だった。一応メルカリの事件とかは知ってて、ああやべえなCache-Control周り、とかは思ってはいたのだが、所詮対岸の火事であるという認識で、必要になった時に調べられる様に覚えておこう、ぐらいのものだった。

この辺りのハイトラフィックWebに対する地力の無さが敗因だったと言える。

一方で、運が悪かったというか問題の特性によって敗北した点もあると思っている。いくらいきなり無茶なパフォーマンス改善が降ってきたとして、そのサービスがCDNを使っているかどうかと、どういうCDNを使っているかの情報すら無いままサーバーサイドの改善にかかることはまずあり得ないだろうと思う。(まあ、この業界は想像の斜め下とか余裕で越えてくるので分からんけど)

またCDNの導入がされててS3やGCSに類するオブジェクトストレージが使えないというのも、イマイチ考えにくい。今回、ローカルのファイルシステムから画像をサーブした結果ファイルの更新時刻の罠にハマってしまったが、仕事で関わったサービスで画像をサーブするのにローカルのファイルシステムを使ったことは多分一度もなかったと思う。CDNを使うぐらいまで成長したサービスが、そういう環境が無いってのはそんなに多くはないだろう。(オンプレで開発してきて場当たり的な改善とCDNでギリギリ何とかしながら成長してしまったサービスとかならあり得ないとも言えない気はする)

というわけで、今回の問題は本当に世界レベルからのアクセスがあるB2CのハイトラフィックWebと向き合ってきた人間がやはり有利だったと思う。まあ、それがWebサービスの現実であり地力の違いである、と言われればグウの音も出ないのだが……。あー辛い……。

今回の我々のチームは全員がB2B業であり、ミドルウェアやら集計SQLやら分散処理システムやらワークフローを見るのがメインみたいな人間ばかりで、これは完全に最近のWebの現実というものに立ち向かえていなかったと言える。CDN使っててS3の無い世界なんて知らねえよww

まあ、私の様な人間はクラウドのぬるま湯に漬かって生きているのだなあ、ということを実感した1日ではあった。

今回得た教訓は、CDNの仕様は厳密に確認しなければならないということと、ローカルファイルシステムからコンテンツをサーブする時はファイルの更新時間を揃えなければいけない、ということだ。まあ後者の知見はよっぽどでないと使う機会は無さそうだが。(未来のISUCONとかぐらいっぽい)

いやー、S3って本当便利ですよね。

データ分析基盤構築入門の献本をいただいた

先日、著者のデータ分析基盤構築入門の著者の一人である@yoshi_kenさんより、献本をいただきました。

http://gihyo.jp/book/2017/978-4-7741-9218-5

f:id:joker1007:20171006045932j:plain

この本は、Fluentdと、ElasticSearch, Kibanaを中心にしたデータの可視化と分析を行える基盤を実際に構築する手順やその意義等が詳細に書かれた本です。

少し遅くなってしまったのですが、やっと一通り目を通すことができました。

序盤はデータ分析を行うことの意義や、分析を行うためのデータ設計、ETLやデータフローの構築が何故必要なのか丁寧に解説されています。最近私も似た様な分野で仕事をしているので、その通りだなあと頷くことが多い内容でした。

中盤はfluentdの解説、後半はElasticSearchとKibanaについての解説が中心です。かなりconfigサンプルが多いので始めてこういうミドルウェアを触る人にとっては助けになる内容だと思います。

fluentdはv0.12系で書かれていて、現在でも安定板はまだ0.12系の扱いですが、ちょうどv0.14系への移り変わり時期と被ってしまっているのが、残念な所です。軽くフォローはされていますが、基本的な内容はv0.12系をベースに書かれています。私も技術書を書いたことがあるので想像できますが、こういうバージョン移行期の執筆は書く側としてももどかしい思いをする所だと思います。しかし、ほとんどの内容はそのまま活用できますし、fluentdのアーキテクチャや構成パターン、運用TIPS等、幅広く網羅されていて、とても良い内容でした。

そして、最後にAppendixとしてfluentdのプラグインまとめとembulk, digdagについての解説があるんですが、Appendixが異様に分厚くて、これは普通にパート1つ分ぐらいある様なボリュームでしたw 多分、日本語の書籍でfluentd, embulk, digdagについてまとめて解説してる本は貴重だと思います。

embulkの解説の中で、私が作ったembulk-filter-ruby_procを活用している例が紹介されていて、なるほど、ちゃんと誰かの役に立ってるんだと嬉しくなりました。ご紹介いただきありがとうございます!

プラグインまとめでは、私がメンテしてたり作ったりしているプラグインもいくつか紹介されています。この本を読んだ人にとって使えそうなものがあったら幸いですね。

私の仕事でも、fluentdやembulkはかなり活用しており、この本のfluentdの解説はとても手厚いので同僚にオススメしたい感じでした。

今回は、献本していただきありがとうございました!

(そういえば、技術書の献本をいただいたのは初めてかも)

RubyKaigi 2017の記録

先日、広島で開催されたRubyKaigi 2017に行ってきました。

相変わらず各トークのテクニカル度合いが異常にハイレベルで刺激的なカンファレンスでした。

最近、静的解析やRubyと型の関係がホットトピックであることもあり、ripperやparser gem等のRubyパーサやRubyVM::Iseq周りを触っているスピーカーが多く、コアに突っ込んでいく人のアクティビティにはとても刺激を受けました。 個人的にはIseqのバイナリ表現とソースコードの変換を活用してマクロの様なことを実現していたCompiling Rubyという発表が印象的でした。 後、毎年ラストのキーノートはやってることが凄過ぎて、圧倒されるばかりですね。

私自身は、メインのスピーカーとして登壇することはできませんでしたが、LTスピーカーとして話をする機会がもらえたので、そこで登壇してきました。

f:id:joker1007:20170927024108j:plain From RubyKaigi 2017 | Flickr

壇上から写真が撮れるのは嬉しい。 f:id:joker1007:20170919174020j:plain

内容は、Refinementsの貴重なユースケースと、Refinementsにbinding_ninjaという拙作のgemを組み合わせた黒魔術の例を示す、という話でした。

binding_ninjaは、C Extensionとして実装することで、メソッドの呼び出し元のコンテキストのbindingを暗黙的に引数に組み込んで渡す、というgemです。 何のことやら良く分からんと思うので、リンク先のREADMEを読むとどういうものか伝わるかと思います。 binding_of_callerという有名なgemがあるのですが、それの機能限定、高速化版みたいなものです。

後から聞いた感じだと、喋りの勢いと技術的なマニアックさが良い按配だったらしく、割と好評だったみたいです。いやー、ウケて良かったー……。

また、2日目の夜は多くのDrink upが開催されてましたが、私は永和システムマネジメント様がスポンサードしていたDrink upでスタッフとして日本酒の選定をさせてもらいました。

日本酒を大量に準備してのDrink upは二度目で、前回もとても好評だったことや、永和というネーミングバリューもあり、一般参加者の募集は即日完売だったらしく、いやースタッフで良かったーと思いましたね。

RubyKaigi 2日目で「Food, Wine and Machine Learning: Teaching a Bot to Taste」というタイトルで登壇していたMai Nguyenさんが参加してくれて、ワインを愛する彼女に日本酒を振る舞うことができたことを嬉しく思っています。

Drink upも好評に終わって最高の夜でした。

f:id:joker1007:20170919185609j:plain f:id:joker1007:20170919185615j:plain

今回、お店側のスタッフの人がかなりサポートしてくれて、同じグループの日本酒バルのお店からメンバーを派遣してくれた様で、お酒を振る舞う上でめちゃくちゃ助かりました。ちゃんと味を見た上で分かり易くグルーピングして並べてくれたし、好みの味に対するお酒のリコメンドもしっかり応えてくれました。

名刺を貰ったので、もしまた広島に行く機会があれば、是非足を運びたいお店ですね。

f:id:joker1007:20170927180620j:plain

その後、Ruby Karaokeまで参加し、大体最後まで居たんですが、ここでめちゃくちゃ消耗して3日目のセッションの半分以上を聞きそびれるという失態を犯すことに……。 まあその後、海外から来てRuby Karaokeに参加してくれた人に軽くお礼の言葉を貰ったんで、まあいいかーという気になりました。救われた感じw

結局、3日目の夜も大体朝方までデカ外人(@dekagaijin)パークで飲んでいて、ヘロヘロのまま宿に戻ったら、チェックアウトが10時だったので9時ぐらいに起きる羽目になり、ロクに広島観光をする余力もなく、お好み焼きだけ食べて東京に戻る、という感じでした。 本当は、もうちょっと宮島ぐらいまで行ったりしたかったんですが、家まで帰り着いた時の自分のコンディションを考えると、帰って正解だったな、と思う。

来年は仙台ということで、東京からだと広島より行き易いし、ジョジョの聖地である杜王町についにRubyKaigiが!って感じなので、何とかCFPに応募できるぐらいは頑張りたいと思います。

穴子飯美味かった。 f:id:joker1007:20170918213147j:plain

穴子刺とハモとメゴチ。ハモは西日本に限る。 f:id:joker1007:20170918202018j:plain

辛いのはそんなに得意ではないが山椒は好きなので、広島の汁なし担々麺は良かった。 f:id:joker1007:20170918120329j:plain

私的リモートワークの良い点、悪い点

Twitterでちらっと見かけたので、自分も良い点と悪い点をまとめてみようと思う。

良い点

  • 電車に乗らなくて良い
  • ミーティング開始の5分前まで寝てられる
  • 人の話し声がしない
  • 話しかけられずに済む
  • 疲れたらいつでもベッドにダイブできる
  • ちゃんとアウトプットしてれば、仕事している様に見せなくてもいい
  • 寝間着で仕事ができる
  • ハイスペックなPCが使える
  • WiFiが不安定みたいなトラブルが少ない
  • 良い椅子が使える
  • ヘッドホン無しで音楽が聞ける
  • いきなり歌い出しても、誰も変な目で見ないし、迷惑がかからない

基本的に引き篭りなので、自宅の環境が快適になる様に腐心した結果、大抵のオフィスより自宅の方が執務環境として優秀であり、自宅より仕事に関する物の水準が高い会社というのをほぼ見たことがない。

そして、日常生活において、歌うというのが精神の健康上とても大事なので、音楽を聞きながら突然歌い出したりしたいのだが、出社してそれをやると頭が致命的な人に思われてしまうし人に迷惑がかかるので、これが最も重要と言えるかもしれない。 カーレンジャーのOPとか聞いてたら「レッツゴー!」とか言いたいし、キングゲイナーのOP聞きながら「キング、キング、キングゲイナー」って歌わずに済ます方が難しい。(そんな曲を仕事中に聞くな、という話であるが……) 実際、すげー辛いテストコードの修正作業とか無意識で歌えるテンション高めの曲とか歌いながら無理やりドライブすると、結構進捗するので勢いってのは大事だと思う。

悪い点

  • チームメンバーとのコミュニケーション量は減る
  • たまに飲みにいく話になった時に、場所が渋谷とか新宿だと行くのが面倒くさい
  • ギリギリまで寝てようと調整し過ぎて寝坊する、疲れたので仮眠を取ろうとして寝過ぎる
  • 生活リズムが無限にズレていく
  • 仕事を止めるタイミングが無いので無限に仕事をしてしまう
  • 飯を食わなくなり、アルフォートで生活する様になる
  • 運動不足が加速する
  • 職場までの距離に対する我慢が足りなくなってくる
  • 歌いながら仕事をすることに慣れてしまい、たまに外に出た時に突然歌い出す危険が増す

一番問題なのは仕事し過ぎではないかと思う。中断するタイミングも特に無いので飯を食うのが面倒臭くなり、冷蔵庫にストックしてあるアルフォートに頼ることになる。幸いにして家の近くにタリーズがあるので、考えごとやら軽く本読む時にタリーズまで行ってついでにサンドイッチ食ったりする様にしている。

生活リズムのズレを是正する仕組みも無いので、自分の睡眠傾向だと生活リズムがえらいことになる。最近は朝の7時から8時に寝て12時から14時に起きることが多い。半年前ぐらいまでは5時には寝れてたんだが……。そして、ミーティングに合わせて無理やり起きたりするので、かなり辛いことになる。

総じて肉体にじわじわダメージが蓄積されていく。自分がどうしても不健康な生活に向かっていくからなのだが、意思の力とかではどうしようもない感じ。

しかし、基本的に肉体にダメージが溜まるのは根本的に自分の体質やら生活スタイルの問題であり、大なり小なり出社してても同じことなので、仕事をするという点においては、やはり自宅が快適なのであまり自宅から離れたくはない。