kakakakakku blog

Weekly Tech Blog: Keep on Learning!

2019年の振り返りと2020年の抱負

既に1月後半になり,遅くなってしまったけど,毎年恒例の「振り返りと抱負」をまとめておこうと思う.

2019年の振り返り 🎉

幅広く技術を学べた

2019年の目標は「幅広く技術を語れるように学び続ける」としていた.未経験の技術は無限にあるし,苦手意識のある技術領域もある.2019年は意図的に「幅広さ」を意識した1年だった.そして,この戦略は正解だったとも感じる.1年前と比べて「技術的な幅広さ」という観点では少なからず成長実感がある.

ブログのカテゴリを見ても,例えば「Python, React, Envoy, GraphQL, Docker, Ansible, Kubernetes, Prometheus, HashiCorp」などなど,技術領域の制限はせず,様々な技術に挑戦できた.そして幅広く学んだからこそ,さらなる「未経験の技術の発見」もできた.本当に未経験の技術は無限にある!Trello のチケットは日々増えているけど,1年間を通してずっと「楽しく学び続けられた」ことは良かった.

当然ながら「技術的な深さを追い求めること」は課題となる.認識はしている.しかし,仕事柄プロダクションコードを書く機会はないし,2018年まで3年間連続で目標にしていた「実践投入力を高める」場面もないことから,今の自分には「幅広さを追求すること」にプライオリティの高さを感じている.2020年も同じ目標を掲げる.

教える / 教える / 教える

技術講師として,1年間ずっと「教えるとは何か?」を考えていた.考えて,実践し,失敗し,改善する.そんなサイクルを繰り返し,ようやく自分のスタイルに到達できた気がする(あくまで気がする).例えば「ラーニングピラミッド」「経験学習モデル」を意識した教授戦略プランを考えたり,初学者に「どういうところに難しさを感じるか?」というヒアリングをして,初学者の域を過ぎた自分では気付けなくなった「難しさの観点」を整理してみたりもした.

また「ペアプログラミング」「モブプログラミング」のメリットを研修の場に適用し,思考の透明性を高める施策なども実践した.さらに2019年の後半3ヶ月は「テスト駆動開発 x モブプログラミング」を体験してもらう社内研修を計10回も開催した.とにかく1年間ずっと「教える」ことに没頭していたし,天職であると思う.

ブログメンタリングを継続できた

2019年もブログメンタリングを継続できた.2019年にご一緒させて頂いたメンティは「計28名(現メンティ含む)」で,累計は「計44名(現メンティ含む)」となる.冷静に考えると異常な人数規模になってきたと思うけど,「無料なの意味不明なんですけど!」という褒め言葉を頂くと嬉しくなり,まだまだ続けられそうな気がする.なお,引き続き応募倍率が高くお断りをすることもあるし,募集期間外の問い合わせも増えてきているけど,ベストエフォートで頑張っているため,お待ち頂ければなーというところ.

インプット/アウトプット 💡

登壇

2019年の登壇は計1回だった.「DevLOVE X」で登壇する機会を頂けたのは非常に嬉しかった.自分で言うのもアレだけど,参加者層のペルソナを相当考えたし,練習もしたし,鬼スベリ覚悟のギャグも投入したし,今でも発表会場の雰囲気を思い出せる.そして Togetter を見直すと今でも笑える.詳しくは以下の記事に載せてあるから是非見てもらえると!

kakakakakku.hatenablog.com

勉強会

2019年に参加した勉強会も,登壇した「DevLOVE X」1回だった.過去数年間から考えると明らかに少ないけど,2019年は「勉強会に参加する時間を他の作業時間に回す」という取捨選択を意図的にしていた.そして,もう1個の理由は「ノドのケア」で,仕事柄ノドが生命線だったりもするため「不特定多数の人が集まる場を避けていた」という背景もある(移動中も常にマスクをしている).とは言え,2020年はもう少し参加したいと考えている.

kakakakakku.hatenablog.com

プルリクエスト

2019年は計17個のプルリクエストを OSS に送ることができた.詳細は既にまとめてある.

ブログ

2019年も「週1回」のノルマを達成し「計72記事」を書いた.1年間を通して,1記事も書けなかった週はなく,確実に習慣化はできているなと再確認できた.そして「週x回」のノルマ生活は2015年から5年も続いていることになる.2019年は「幅広く技術を語れるように学び続ける」という目標もあり,すぐ忘れる自分のために書いた記事も多かったけど,累計ブクマ数は2019年で「12281 → 14771 (+2490)」という結果だった.ブクマを集めることは目的ではないけど,誰かの役に立つ記事が書けているのであれば,嬉しいなと思う.

f:id:kakku22:20200121000243p:plain

なお,200 ブクマを超えた記事は5記事だった.

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

書籍

出版に直接携わったわけではないけど,O'Reilly Japan から春に出版された「分散システムデザインパターン」の出版レビューに参加できたことは非常に印象に残っている.今でもパラパラと読み直している.詳しくは以下の記事にまとめてある!

kakakakakku.hatenablog.com

読んだ本

2019年は「計16冊」読んだ.特に忙殺されていた月は書評記事を書けてないけど,それでも2018年よりも多く読むことができた.

Mackerel アンバサダー

光栄なことに2019年3月に「Mackerel アンバサダー」に就任させて頂いた.定量的なアウトプットの規定は(現在のところ)なく,やはり個人的にはブログに期待をして頂いているのかなと感じていたため「3ヶ月に1記事(2019年に3記事)」という目標を掲げていた.ただし,結果としては2記事で未達成となり,反省点が多くある.2020年も同じノルマ「3ヶ月に1記事」で頑張る.

2020年の抱負 ✨

幅広く技術を語れるように学び続ける

振り返りにも書いた通り,2019年に掲げた「幅広く技術を語れるように学び続ける」という目標がうまくハマったため,2020年も継続する.フロントエンド/バックエンド/インフラ/マネジメントなど,技術領域の制限はせず,好奇心のままに貪欲に学び続けていく.特に「OSS」など,オープンな技術を主軸にすることも2019年と変わらず継続する.

そして,2020年は新たに「教えてもらう」という施策にも取り組んでみたいと思う.背景としては,技術講師として「教えること」を追求しているし,その価値を実感しているのにも関わらず,自分自身は「1年間ずっと独学ばかりしていた」というギャップに悩んでいた.特に新しい技術に入門するときに独学をするのは非効率だし,誤ったベクトルに進んでしまうこともある.そして,何よりもまず「自分自身が気付いていないことは調べることすらできない」という問題があるため,積極的に「達人を味方」にして,学んでいく.教えてー!

定量的な目標は過去2年間と変わらず,以下となる.

  • ブログは変わらず「週1記事」ノルマ
  • ストレッチゴールとして年間「70記事」

まとめ

2020年も攻めていくぞ 🔥

過去の振り返り

Envoy の route.HeaderMatcher を使う「Implementing Blue / Green Rollouts」を試した

今回は「Try Envoy」「Implementing Blue / Green Rollouts」を紹介する.Envoy でサポートされている様々なルーティング設定の中から「HTTP Header ベースルーティング」「加重ラウンドロビン」を学べる.

Implementing Blue / Green Rollouts

手順は以下の「計6種類」ある.

  • Step.1 「Envoy Base Configuration」
  • Step.2 「Header-Based Routing」
  • Step.3 「Deploy Header-Based Routing」
  • Step.4 「Weighted Load Balancing」
  • Step.5 「Rollout 20% Traffic」
  • Step.6 「Rollout 100% Traffic」

www.envoyproxy.io

www.katacoda.com

Step.1 「Envoy Base Configuration」

Step.1 では envoy.yaml を読み解き,クイズに正解する必要がある.理解度確認になって非常に良いと思うし,Katacoda ってクイズ形式も作れるんだ!というプラットフォームの機能の幅広さに驚いたりもした.

  • Question: How many routes have been defined within the configuration?
  • Question: How many clusters have been defined within the configuration?

f:id:kakku22:20200112132842p:plain

Step.2 「Header-Based Routing」

ユーザーに影響を出さず,新機能をテストすることを「ダークリリース (Dark Releases)」と言う(とコンテンツに書いてある).Envoy の route_config は上から順番に比較し,最初に一致したルーティング設定を使うことになるため,今回は /service/2 に対して2種類のルーティング設定をしている.今回は HTTP Header x-canary-versionservice2a という値が設定されている場合に Cluster service2a にルーティングする.

route_config:
  virtual_hosts:
  - name: backend
    domains:
    - "*"
    routes:
    - match:
        prefix: "/service/1"
      route:
        cluster: service1
    - match:
        prefix: "/service/2"
        headers:
        - name: "x-canary-version"
          exact_match: "service2a"
      route:
        cluster: service2a
    - match:
        prefix: "/service/2"
      route:
        cluster: service2

Envoy は routes の設定として route.RouteMatch を使えば,細かくルーティングを制御できる.今回は route.HeaderMatcher を使って HTTP Header ベースのルーティングを実現している.高機能ロードバランサとして機能が揃っている!

www.envoyproxy.io

Step.3 「Deploy Header-Based Routing」

次に Envoy と katacoda/docker-http-server を起動する.katacoda/docker-http-serverv1 を2個 / v2 を2個 / v3 を1個起動する.構成図は以下のようになる.

$ docker run -d --name proxy1 -p 80:8080 -v /root/:/etc/envoy envoyproxy/envoy

$ docker run -d katacoda/docker-http-server:v1
$ docker run -d katacoda/docker-http-server:v1
$ docker run -d katacoda/docker-http-server:v2
$ docker run -d katacoda/docker-http-server:v2
$ docker run -d katacoda/docker-http-server:v3

f:id:kakku22:20200112132915p:plain

katacoda/docker-http-server とコンテナ ID の関係を整理しておくと,以下のようになる.

$ docker ps -q | xargs -n 1 docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} {{ .Config.Hostname }} {{ .Config.Image }}' | sed 's/ \// /'
172.18.0.7 fdf5019381c2 katacoda/docker-http-server:v3
172.18.0.6 64724167bc8d katacoda/docker-http-server:v2
172.18.0.5 63a9ff40f850 katacoda/docker-http-server:v2
172.18.0.4 efab265b509a katacoda/docker-http-server:v1
172.18.0.3 c985d2391163 katacoda/docker-http-server:v1
172.18.0.2 9814e5b162f4 envoyproxy/envoy

動作確認として,まず http://localhost/service/2 にリクエストを送ると,Cluster service2 を経由して efab265b509a にルーティングされる.そして http://localhost/service/2 に HTTP Header を乗せてリクエストを送ると,Cluster service2a を経由して 63a9ff40f850 にルーティングされる.構成図の通りになっている.

# HTTP Header なし
$ curl http://localhost/service/2
<h1>This request was processed by host: efab265b509a</h1>
$ curl http://localhost/service/2
<h1>This request was processed by host: efab265b509a</h1>

# HTTP Header あり
$ curl -H "x-canary-version: service2a" http://localhost/service/2
<h1>New Release! Now v2! This request was processed by host: 63a9ff40f850</h1>
$ curl -H "x-canary-version: service2a" http://localhost/service/2
<h1>New Release! Now v2! This request was processed by host: 63a9ff40f850</h1>

Step.4 「Weighted Load Balancing」

次は「カナリアリリース」のように使える「加重ラウンドロビン」を試す.ルーティング設定に weighted_clusters を追加すると,weight に設定した割合に従ってルーティングされる.今回は /service/3 にリクエストを送ると 80% は Cluster service3a に,そして 20% は Cluster service3b にルーティングされる.

route_config:
  virtual_hosts:
  - name: backend
    domains:
    - "*"
    routes:
    - match:
        prefix: "/service/1"
      route:
        cluster: service1
    - match:
        prefix: "/service/2"
        headers:
        - name: "x-canary-version"
          exact_match: "service2a"
      route:
        cluster: service2a
    - match:
        prefix: "/service/2"
      route:
        cluster: service2
    - match:
        prefix: "/service/3"
      route:
        weighted_clusters:
          clusters:
          - name: service3a
            weight: 80
          - name: service3b
            weight: 20

Step.5 「Rollout 20% Traffic」

設定変更を反映するため Envoy を再起動する.構成図は以下のようになる.

f:id:kakku22:20200112132930p:plain

さっそく http://localhost/service/3 に対してリクエストを送ると,割合に従ってルーティングされる.繰り返し実行していると,キレイに 80% / 20% にならないこともあった.

$ for i in {1..10}; do curl -s http://localhost/service/3; done
<h1>New Release! Now v2! This request was processed by host: 64724167bc8d</h1>
<h1>New Another Release! Now v3! This request was processed by host: fdf5019381c2</h1>
<h1>New Release! Now v2! This request was processed by host: 64724167bc8d</h1>
<h1>New Release! Now v2! This request was processed by host: 64724167bc8d</h1>
<h1>New Release! Now v2! This request was processed by host: 64724167bc8d</h1>
<h1>New Release! Now v2! This request was processed by host: 64724167bc8d</h1>
<h1>New Release! Now v2! This request was processed by host: 64724167bc8d</h1>
<h1>New Release! Now v2! This request was processed by host: 64724167bc8d</h1>
<h1>New Another Release! Now v3! This request was processed by host: fdf5019381c2</h1>
<h1>New Release! Now v2! This request was processed by host: 64724167bc8d</h1>

なお,今回は手順を簡単にするために Envoy を再起動したけど,Route Discovery Service (RDS) を使えば,自動的に反映できるようになる.xDS 関連は以下の Try Envoy が参考になる.

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

Step.6 「Rollout 100% Traffic」

最後は割合を 100% にする.100% なら weighted_clusters を使う必要はなさそうだけど,実際に動くことを確認する手順になっていた.

route_config:
  virtual_hosts:
  - name: backend
    domains:
    - "*"
    routes:
    - match:
        prefix: "/service/1"
      route:
        cluster: service1
    - match:
        prefix: "/service/2"
        headers:
        - name: "x-canary-version"
          exact_match: "service2a"
      route:
        cluster: service2a
    - match:
        prefix: "/service/2"
      route:
        cluster: service2
    - match:
        prefix: "/service/3"
      route:
        weighted_clusters:
          clusters:
          - name: service3b
            weight: 100

構成図は以下のようになる.

f:id:kakku22:20200112133003p:plain

まとめ

  • 「Try Envoy」のコンテンツ「Implementing Blue / Green Rollouts」を試した
  • Envoy は「高機能ロードバランサ」の側面として様々なルーティング設定ができることを学んだ

引き続き,進めていくぞ!

プルリクエスト

試しながら気付いた誤りを修正してプルリクエストを送っておいた!

github.com

Try Envoy 関連

Material-UI の GridList コンポーネントを実装する

前回の記事から少し時間がたってしまったけど,Material-UI を使ったプロトタイプ開発を続けている.今回は GridList コンポーネントをサンプルコードを参考に実装しながら理解を深めていく.グリッドリストはフォトリストのようにコンテンツを並べる UI のことを言う.過去には List コンポーネントと Snackbars コンポーネントの記事を書いていて,コンポーネントの調査シリーズも定期的に書いていく.

material-ui.com

なお,実装したサンプルコードは GitHub に公開してある.TypeScript で create-react-app を実行してから実装を進めた.記事に載せるコードはポイントを限定し抜粋するため,実際にコード全体を見る場合は GitHub を参照して頂ければと!

$ create-react-app sandbox-material-ui-grid-list --template typescript
$ cd sandbox-material-ui-grid-list

$ npm install @material-ui/core
$ npm install @material-ui/icons

$ yarn start

github.com

今までは create-react-app --typescript を使っていたけど,最新版の v3.3.0 から以下の警告が出るようになっていた.--typescript オプションは廃止になり,今後は create-react-app --template typescript を使う必要がある.覚えておこう.

The --typescript option has been deprecated and will be removed in a future release.
In future, please use --template typescript.

GridList コンポーネント

GridList コンポーネントはグリッドリストの「全体枠」を定義する.

主要なパラメータは2個ある.まず,cellHeight プロパティを使うと,グリッドリストの「高さ」を設定できる.ピクセル固定もできるし,自動なら auto も設定できる.次に,cols プロパティを使うと,グリッドリストの「タイル数(横)」を設定できる.GridList コンポーネントで使えるプロパティ一覧は以下のドキュメントに載っている.

material-ui.com

サンプルコードの一部を載せておく.

const App: React.FC = () => {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <GridList cellHeight={200} className={classes.gridList} cols={3}>
      </GridList>
    </div>
  );
}

GridListTile コンポーネント

GridListTile コンポーネントはグリッドリストの「タイル」を定義する.

GridList コンポーネントと GridListTile コンポーネントを組み合わせることにより,「全体枠」の中に「タイル」を配置できる.GridList 側で設定した cols に対して,GridListTile 側でさらに cols を設定できる.cols="1" にすれば「等間隔にタイルを敷き詰める」ことができるし,cols="1"cols="2" を組み合わせれば「特定のタイルのサイズを変える」こともできる.GridListTile コンポーネントで使えるプロパティ一覧は以下のドキュメントに載っている.

material-ui.com

サンプルコードの一部を載せておく.

const App: React.FC = () => {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <GridList cellHeight={200} className={classes.gridList} cols={3}>
        <GridListTile key="cat" cols="2">
          <img src={cat} alt="cat" />
        </GridListTile>
        <GridListTile key="deer" cols="1">
          <img src={deer} alt="deer" />
        </GridListTile>
        <GridListTile key="kingfisher" cols="1">
          <img src={kingfisher} alt="kingfisher" />
        </GridListTile>
        <GridListTile key="koala" cols="1">
          <img src={koala} alt="koala" />
        </GridListTile>
        <GridListTile key="pelikan" cols="1">
          <img src={pelikan} alt="pelikan" />
        </GridListTile>
        <GridListTile key="rabbit" cols="1">
          <img src={rabbit} alt="rabbit" />
        </GridListTile>
        <GridListTile key="tiger" cols="2">
          <img src={tiger} alt="tiger" />
        </GridListTile>
      </GridList>
    </div>
  );
}

実際に動作確認をすると,cols="2" に設定したネコとトラは2タイルを結合したサイズになっている.なお,動物の素材は Pixabay から取得している.

f:id:kakku22:20200112123227p:plain

GridListTileBar コンポーネント

GridListTileBar コンポーネントはタイルの上に表示する「追加情報」を定義する.

主要なパラメータは3個ある.まず,titlesubtitle を使うと追加情報をタイルの上に表示できる.さらに,actionIcon を使うと「ボタンを押したら○○」というトリガーの実装と連携することもできる.GridListTileBar コンポーネントで使えるプロパティ一覧は以下のドキュメントに載っている.

material-ui.com

サンプルコードの一部を載せておく.titletitle + subtitletitle + subtitle + actionIcon の計3パターンを実装した.

const App: React.FC = () => {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <GridList cellHeight={200} className={classes.gridList} cols={3}>
        <GridListTile key="cat" cols="2">
          <img src={cat} alt="cat" />
          <GridListTileBar
            title="Cat"
          />
        </GridListTile>

        {/* 中略 */}

        <GridListTile key="koala" cols="1">
          <img src={koala} alt="koala" />
          <GridListTileBar
            title="Koala"
            subtitle="So Cute !"
          />
        </GridListTile>

        {/* 中略 */}

        <GridListTile key="tiger" cols="2">
          <img src={tiger} alt="tiger" />
          <GridListTileBar
            title="Tiger"
            subtitle="So Cool !"
            actionIcon={
              <IconButton className={classes.icon}>
                <InfoIcon />
              </IconButton>
            }
          />
        </GridListTile>
      </GridList>
    </div>
  );
}

実際に動作確認をすると,ネコとコアラとトラに追加情報が表示されている.

f:id:kakku22:20200112123308p:plain

ListSubheader コンポーネント

ListSubheader コンポーネントはグリッドリストの中に「区切り」を定義する.

GridListTile コンポーネントの中に ListSubheader コンポーネントを含めるため,サイズなどは GridListTile コンポーネントの cols プロパティを使う.ListSubheader コンポーネントで使えるプロパティ一覧は以下のドキュメントに載っている.

material-ui.com

サンプルコードの一部を載せておく.写真を撮影した年を区切りとして追加した.

const App: React.FC = () => {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <GridList cellHeight={200} className={classes.gridList} cols={3}>
        <GridListTile key="Subheader" cols={3} style={{ height: 'auto' }}>
          <ListSubheader component="div">2018</ListSubheader>
        </GridListTile>
        <GridListTile key="cat" cols="2">
          <img src={cat} alt="cat" />
          <GridListTileBar
            title="Cat"
          />
        </GridListTile>
        <GridListTile key="deer" cols="1">
          <img src={deer} alt="deer" />
        </GridListTile>

        {/* 中略 */}

        <GridListTile key="Subheader" cols={3} style={{ height: 'auto' }}>
          <ListSubheader component="div">2019</ListSubheader>
        </GridListTile>
        <GridListTile key="rabbit" cols="1">
          <img src={rabbit} alt="rabbit" />
        </GridListTile>
        <GridListTile key="tiger" cols="2">
          <img src={tiger} alt="tiger" />
          <GridListTileBar
            title="Tiger"
            subtitle="So Cool !"
            actionIcon={
              <IconButton className={classes.icon}>
                <InfoIcon />
              </IconButton>
            }
          />
        </GridListTile>
      </GridList>
    </div>
  );
}

実際に動作確認をすると,2018年の区切り(ヘッダー)と2019年の区切り(ヘッダー)が表示されている.

f:id:kakku22:20200112123336p:plain

まとめ

Material-UIGridList コンポーネントを実装しながら理解を深めた.コード量を少なくグリッドリストの実装ができて便利だった.引き続きコンポーネント調査を続けていくぞ!

Material-UI 関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

2019年(7-12月)のプルリクエストを振り返る

OSS に送ったプルリクエストを振り返ろうと思う.プルリクエストの振り返りは2016年からしているけど,2019年は前半に多く送った背景もあり,既に「2019年(1-6月)」の期間で記事を書いている.今回は後半として「2019年(7-12月)」を振り返ろうと思う.後半の累計は「計5件」となり,2019年全体だと「計17件」となる.過去の振り返りは以下にある.

プルリクエストを振り返るための検索

プルリクエストを振り返るために GitHub の検索条件を使う.今回は「2019年(7-12月)」に限定する必要があるため created:2019-07-01..2019-12-31 を使う.

is:pr is:public author:kakakakakku -user:kakakakakku created:2019
is:pr is:public author:kakakakakku -user:kakakakakku created:2019-07-01..2019-12-31

2019/11

awsdocs/aws-cloudformation-user-guide

ドキュメントを読みながら AWS CloudFormation テンプレートの写経をしていたら,プロパティ名に誤りを発見したため,修正した.

github.com

awslabs/aws-sam-cli

AWS SAM を検証していたら,CLI のエラーメッセージに誤ったオプションが記載されていたため,修正した.

github.com

2019/12

envoyproxy/katacoda-scenarios

11月から Envoy を学ぶため「Try Envoy」のコンテンツを活用している.Envoy のドキュメントにデッドリンクがあったり,名称が統一されていなかったり,進めていると気になる誤りがあったため,コツコツと修正している.Merge はされているものの,実際に Katacoda にデプロイされるタイミングはわからず,気長に待ちたいと思う.

github.com github.com github.com

まとめ

2019年(7-12月)は「計5件」のプルリクエストを送ることができた.日に日にコードを書く機会が減っていることもあり,プルリクエストを送る機会も減っているけど,来年もコツコツと頑張る!

Envoy の Health Checking と Outlier Detection の違いを学べる「Detecting Down Services with Health Checks」を試した

今回は「Try Envoy」「Detecting Down Services with Health Checks」を紹介する.高可用性のために Envoy でサポートされている「ヘルスチェック (Health Checking)」「外れ値検出 (Outlier Detection)」を学べる.

Detecting Down Services with Health Checks

手順は以下の「計8種類」ある.

  • Step.1 「Proxy Configuration」
  • Step.2 「Add Health Check」
  • Step.3 「Start Proxy」
  • Step.4 「Failed Services」
  • Step.5 「Healthy Services」
  • Step.6 「Total Failure」
  • Step.7 「Outlier Detection Configuration」
  • Step.8 「Testing Outlier Detection」

www.envoyproxy.io

www.katacoda.com

Step.1 「Proxy Configuration」

まず,用意された envoy.yaml の設定を確認する.もう完全に読み慣れたと思う.ポイントは clusters で,全てのリクエストを 172.18.0.3172.18.0.4 にラウンドロビンでルーティングする.例えば「もし一部のエンドポイントに障害が発生した場合」に Envoy ではどう対応したら良いのだろう?そこで「ヘルスチェック (Health Checking)」を使う.

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 8080 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: backend
              domains:
                - "*"
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: targetCluster
          http_filters:
          - name: envoy.router
  clusters:
  - name: targetCluster
    connect_timeout: 0.25s
    type: STRICT_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    hosts: [
      { socket_address: { address: 172.18.0.3, port_value: 80 }},
      { socket_address: { address: 172.18.0.4, port_value: 80 }}
    ]

Step.2 「Add Health Check」

ヘルスチェックは clusters の中に設定する.代表的なパラメータは以下となる.

  • interval : 間隔
  • unhealthy_threshold : 異常と判断する閾値
  • healthy_threshold : 正常と判断する閾値
  • http_health_check.path : ヘルスチェックをする URL

今回は /health に対して「10秒間隔」でヘルスチェックをし,1回成功すると正常と判断する.他にも「Jitter(ランダムな遅延時間)」の設定も入っている.

clusters:
- name: targetCluster
  connect_timeout: 0.25s
  type: STRICT_DNS
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  hosts: [
    { socket_address: { address: 172.18.0.3, port_value: 80 }},
    { socket_address: { address: 172.18.0.4, port_value: 80 }}
  ]
  health_checks:
    - timeout: 1s
      interval: 10s
      interval_jitter: 1s
      unhealthy_threshold: 6
      healthy_threshold: 1
      http_health_check:
        path: "/health"

他にも細かなパラメータは多くあるため,必要に応じてドキュメントを参照する.

www.envoyproxy.io

Step.3 「Start Proxy」

さっそく Envoy を起動する.同時にバックエンドのホストとして katacoda/docker-http-server:healthy を2個起動する.コンテナイメージ(healthy タグ)の解説は書かれてなく,あくまで予想だけど,デフォルトでヘルスチェックに成功する実装になっていると思う.そして curl で状態を変えることもできる.

$ docker run -d --name proxy1 -p 80:8080 -v /root/:/etc/envoy envoyproxy/envoy

$ docker run -d katacoda/docker-http-server:healthy

$ docker run -d katacoda/docker-http-server:healthy

Envoy にリクエストを送ると,正常に動いている.やっと検証環境が整った!

$ curl localhost
<h1>A healthy request was processed by host: daab80baf4ae</h1>

$ curl localhost
<h1>A healthy request was processed by host: 80461e934cfd</h1>

$ curl localhost
<h1>A healthy request was processed by host: daab80baf4ae</h1>

Step.3 完了時点で構成図は以下のようになる.

f:id:kakku22:20200107012133p:plain

Step.4 「Failed Services」

意図的に障害を発生させるため,172.18.0.3 に対して /unhealthy エンドポイントを呼び出す.すると,レスポンスに unhealthy request と表示される.レスポンスコードも 500 となり,1個のエンドポイントを落とすことができた.

$ curl 172.18.0.3/unhealthy

$ curl 172.18.0.3 -i
HTTP/1.1 500 Internal Server Error
(中略)
<h1>A unhealthy request was processed by host: daab80baf4ae</h1>

実際に while を使って 0.5 秒ごとに curl をすると,unhealthy_threshold に該当したタイミングから unhealthy request の発生は止まる.期待通りにヘルスチェックが動いていることを確認できる.

$ while true; do curl localhost; sleep .5; done
<h1>A unhealthy request was processed by host: daab80baf4ae</h1>
<h1>A healthy request was processed by host: 80461e934cfd</h1>
<h1>A unhealthy request was processed by host: daab80baf4ae</h1>
<h1>A healthy request was processed by host: 80461e934cfd</h1>
<h1>A unhealthy request was processed by host: daab80baf4ae</h1>

(中略)

<h1>A healthy request was processed by host: 80461e934cfd</h1>
<h1>A healthy request was processed by host: 80461e934cfd</h1>
<h1>A healthy request was processed by host: 80461e934cfd</h1>
<h1>A healthy request was processed by host: 80461e934cfd</h1>
<h1>A healthy request was processed by host: 80461e934cfd</h1>

Step.4 完了時点で構成図は以下のようになる.

f:id:kakku22:20200107012153p:plain

Step.5 「Healthy Services」

障害を復旧するため,172.18.0.3 に対して /healthy エンドポイントを呼び出す.すると,またエンドポイントは2個に戻る.

$ curl 172.18.0.3/healthy

$ curl localhost
<h1>A healthy request was processed by host: daab80baf4ae</h1>

$ curl localhost
<h1>A healthy request was processed by host: 80461e934cfd</h1>

$ curl localhost
<h1>A healthy request was processed by host: daab80baf4ae</h1>

Step.6 「Total Failure」

今度は全面障害を発生させるため,172.18.0.3172.18.0.4 に対して /unhealthy エンドポイントを呼び出す.

$ curl 172.18.0.3/unhealthy

$ curl 172.18.0.4/unhealthy;

「Try Envoy」の手順だと Envoy から 503 が返ってくると書いてあるけど,実際にはそうならなかった.全面障害になると,全てのエンドポイントにリクエストをルーティングしているため,期待していた挙動とも異なる.設定変更など,もう少し調査が必要そう.なんだろう.

$ curl localhost -i
HTTP/1.1 500 Internal Server Error
(中略)
<h1>A unhealthy request was processed by host: daab80baf4ae</h1>

$ curl localhost -i
HTTP/1.1 500 Internal Server Error
(中略)
<h1>A unhealthy request was processed by host: 80461e934cfd</h1>

Step.7 「Outlier Detection Configuration」

Step.7 と Step.8 は「外れ値検出 (Outlier Detection)」を試す.ドキュメントを読むと,以下のように表現されている.なお「ヘルスチェック」「外れ値検出」は併用もできる.

  • "Active" Health Checking : ヘルスチェック (Health Checking)
  • "Passive" Health Checking : 外れ値検出 (Outlier Detection)

ActivePassive という表現にもある通り,意図的にリクエストを投げて正常を確認するのが「ヘルスチェック」で,エンドポイントからのレスポンスをダイナミックに確認して正常を確認するのが「外れ値検出」となる.

www.envoyproxy.io

実際のレスポンスを判断材料にするため,設定項目は少なく使うことができる.envoy.yaml は以下のようになり,5xx のレスポンスコードが「3回」連続して返された場合に base_ejection_time に設定した時間はルーティング対象から除外する(Ejection と言う).その後もう1度ルーティング対象になる.

clusters:
- name: targetCluster
  connect_timeout: 0.25s
  type: STRICT_DNS
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  hosts: [
    { socket_address: { address: 172.18.0.5, port_value: 80 }},
    { socket_address: { address: 172.18.0.6, port_value: 80 }}
  ]
  outlier_detection:
      consecutive_5xx: "3"
      base_ejection_time: "30s"

ただし,base_ejection_time は実際にはもう少し複雑で,ドキュメントを読むと以下のように書いてある.ようするにbase_ejection_time に設定した時間に Ejection された回数を掛ける」となり,繰り返し 5xx を返すエンドポイントほど「長時間 Ejection される」アプローチが実装されている.

The base time that a host is ejected for. The real time is equal to the base time multiplied by the number of times the host has been ejected.

www.envoyproxy.io

Step.8 「Testing Outlier Detection」

最後は「外れ値検出」を試す.katacoda/docker-http-server:healthy を追加で2個起動してから outlier_detection の設定をした Envoy も起動する.

$ docker run -d katacoda/docker-http-server:healthy

$ docker run -d katacoda/docker-http-server:healthy

$ docker run -d --name proxy2 -p 81:8080 \
    -v /root/:/etc/envoy \
    -v /root/envoy1.yaml:/etc/envoy/envoy.yaml \
    envoyproxy/envoy

while を使って 0.5 秒ごとに curl をしながら 172.18.0.5 に対して /unhealthy エンドポイントを呼び出すと,計3回 unhealthy が出て,その後は止まっている.期待通りに外れ値検出が動いていることを確認できる.

$ curl 172.18.0.5/unhealthy

$ while true; do curl localhost:81; sleep .5; done
<h1>A healthy request was processed by host: f586da250a29</h1>
<h1>A unhealthy request was processed by host: 932aa85e6053</h1>
<h1>A healthy request was processed by host: f586da250a29</h1>
<h1>A unhealthy request was processed by host: 932aa85e6053</h1>
<h1>A healthy request was processed by host: f586da250a29</h1>
<h1>A unhealthy request was processed by host: 932aa85e6053</h1>
<h1>A healthy request was processed by host: f586da250a29</h1>
<h1>A healthy request was processed by host: f586da250a29</h1>
<h1>A healthy request was processed by host: f586da250a29</h1>

まとめ

  • 「Try Envoy」のコンテンツ「Detecting Down Services with Health Checks」を試した
  • Envoy では「ヘルスチェック (Health Checking)」以外に「外れ値検出 (Outlier Detection)」もあることを学べた

引き続き,進めていくぞ!

プルリクエスト

試しながら気付いた誤りを修正してプルリクエストを送っておいた!

github.com

Try Envoy 関連