Chart.js , vue-chart-jsで、データのフィルターを外部のinputから行う

この記事は個人ブログと同じ内容です

zenn.dev

概要

chart.jsのデータセットの絞り込みをLegend(凡例)のクリックではなくて、chart.jsのコンポーネント外のインプットから行うサンプルを作ってみた。

まとめ

chart.js の表現を超えた凝った表現をする場合は、なかなか面倒そうだということがわかった。
(今回のサンプル以外に、やりたいことあったけど、調べてるうちに作るより、デザインや受け入れ要件と調整するほうが楽やろ!もしくはスクラッチで作ったほうが早いやんか!となったので)
本来の目的は楽することなので、それを忘れないようにしたい。

本題

chartjsのデフォルト

chartjsのデフォルトだと、上記のような凡例をクリックする事により表示項目を絞ることができるが、
下記のように絞り込みのUIを chartjs のコンポーネントから引き剥がしたかったため、試しに作ってみた。
おまけで、カテゴリごとに表示項目絞るみたいなこともできるようになった。
(野菜だけ表示するみたいなことができる)

今回作ったもの

コードはこちらにおいてます
https://codesandbox.io/s/chartjs-toggle-datasets-outer-input-mh0r6

外部から、Legendのフィルタ情報を渡してdatasetをフィルタすることはできなかった。

chartjs にデフォルトで用意されてるだろうと調べてみたけど、入ってなかった。

凡例からのフィルタらしきものをoptions.legend.labels.filter見つけたが、これは、凡例のラベルをフィルタするものであって、凡例のラベルをクリックしたときのdatasetのフィルタには全く関係がなかった。

https://www.chartjs.org/docs/latest/configuration/legend.html#legend-label-configuration

propsで datasets を渡す際に、 外でフィルタした状態のdatasetsを渡す方針に変更

chartjsに用意されてない昨日だとわかった & chartjsを拡張するほど力をかけたくないので、簡単そうな方法「そもそもフィルタしたdatasets渡したらええやん」にきりかえた。
(「力かけたくないなら デフォルトの凡例クリックでのフィルタでいいじゃん」には耳をふさぎます。)

できたもの

やってることは単純で、

  1. filter用の配列(filterItem)をdataに持つ
  2. filterItemはinputや、methodで変え放題
  3. 表示したいdataをchartのコンポーネントに渡す際に、filterItemでフィルタして渡す

https://codesandbox.io/s/chartjs-toggle-datasets-outer-input-mh0r6

<template>
  <div>
    <button @click="fillData()">Randomize</button>

    <hr />
    <button @click="filterByCategory('vegetable')">野菜だけ</button>
    <button @click="filterByCategory('fruit')">果物だけ</button>
    <hr />

    <template v-for="item in dataSetsKey">
      <label
        :for="`check_${item.index}`"
        :key="item.label"
        :style="`color: ${item.color}; margin: 8px;`"
        ><input
          :id="`check_${item.index}`"
          type="checkbox"
          v-model="filterItem"
          :value="item.index"
        />{{ item.label }}</label
      >
    </template>
    <BarChart
      :chart-data="filteredDataCollection"
      :options="{
        responsive: true,
        maintainAspectRatio: false,
        labeling: { display: false },
        legend: {
          display: false,
        },
      }"
    />
  </div>
</template>

<script>
import BarChart from "./components/BarChart.vue";

export default {
  name: "SandBox",
  components: {
    BarChart,
  },
  data() {
    return {
      datacollection: {},
      filterItem: [],
    };
  },
  created() {
    this.fillData();
    this.filterItem = this.datacollection.datasets.map((_s, i) => i);
  },
  computed: {
    filteredDataCollection() {
      const collection = {
        ...this.datacollection,
        datasets: this.datacollection.datasets.filter((_s, i) => {
          return this.filterItem.includes(i);
        }),
      };
      return collection;
    },
    dataSetsKey() {
      return this.datacollection.datasets.map((s, i) => ({
        label: s.label,
        index: i,
        color: s.backgroundColor,
      }));
    },
  },
  methods: {
    filterByCategory(category) {
      this.filterItem = this.datacollection.datasets
        .map((s, i) => {
          if (s.dataCategory === category) return i;
          return -1;
        })
        .filter((s) => s >= 0);
    },
    fillData() {
      this.datacollection = {
        labels: ["1月", "2月", "3月", "4月", "5月", "6月"],
        datasets: [
          {
            label: "ほうれん草",
            backgroundColor: "#4cc36b",
            dataCategory: "vegetable",
            data: [
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
            ],
          },
          {
            label: "なす",
            backgroundColor: "#456dfe",
            dataCategory: "vegetable",
            data: [
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
            ],
          },
          {
            label: "りんご",
            backgroundColor: "#f44b81",
            dataCategory: "fruit",
            data: [
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
            ],
          },
          {
            label: "みかん",
            backgroundColor: "#f48817",
            dataCategory: "fruit",
            data: [
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
              this.getRandomInt(),
            ],
          },
        ],
      };
    },
    getRandomInt() {
      return Math.floor(Math.random() * (50 - 5 + 1)) + 5;
    },
  },
};
</script>

最後に

chartjsで調べたり書いてみたりしていたけど、ライブラリの表現を超える表現をする場合にはやっぱり、なかなかパワーいるなと思った。
楽したくて使ってるのに、カスタマイズ重ねて結局スクラッチのほうが楽だったねってならないように気をつけようとおもた。

PHPerKaigi 2021 ガヤ担当の感想

こちらの記事は個人ブログの転載です

akki-megane.hatenablog.com

まずは 今年も無事にPHPerKaigi 2021 開催できたこと、本当にありがとうございます!

f:id:akki_megane:20210329185249j:plain

スタッフとして

PHPerKaigi 2020 に引き続き、コアスタッフとして参加させていただきました。 当日はTrack-A の担当として、配信の設定やAsk The Speaker のがや(ファシリテーション)をしてました。

Ask The Speaker ・・・登壇後にスピーカー方へ質問や感想を視聴者が言う場です。 今回はDiscord で実施しました。

Ask The Speaker 楽しかった

3日間 Ask The Speaker をしましたが、配信会場的に1部屋しかなく、
他の音が入らないように部屋の隅っこのほうでブツブツPCに喋っている状況でした。

Ask The Speakerは基本的は視聴者方がスピーカーへ質問や感想を述べる場ですが、慣れない状況やオンラインということもあり、
最初に質問等をするにはややハードルが上がっている状況だったので、
取っ掛かりになるように、自分が積極的に質問やディスカッションをするように心がけました。

話が盛り上がると質問が活発になったり、別の参加者から補足事項が出てき新しい知見があったり、
本来カンファレンスの懇親会でするようなざっくばらんな話をこの場でできたのはとても楽しい体験でした。

スピーカー方からも、「Ask The Speaker 素敵な体験でした」、「Ask The Speaker で新しい発見がありました」等好意的な意見を頂いたので、部屋の隅っこで3日間喋り倒した甲斐がありました。

アンカンファレンス 今年も無限LTをした

実は2019年から、毎年恒例で無限LTという狂気のLT回を実施しているんですが、 今年もおかしょい (@okashoi) | Twitter が発起人となり実施しました。

fortee.jp

突発でしたが、20人ぐらい参加してくれて、7人LTが実施できました。
参加者のみなさんありがとうございました!

自分は、技術的負債を返し続ける取り組み というタイトルで発表しました。
当日におかしょいさんから依頼があったのでスタッフ業の合間を縫ってこそこそ作ってました。

18:20~ 開催だったためスタッフは解散し部屋を追い出され、
途方にくれた結果、近くの喫茶店に入り隅っこでコソコソ話してました。
f:id:akki_megane:20210329195716p:plain

無限LTの説明
speakerdeck.com

感想

今年も最高な PHPerKaigi でした!

素晴らしいセッションもたくさん聞けて、明日からのモチベーションをかなりもらいました!

オンラインということもあり、多くの人とはコミュニケーションが取れず、ちょっとさみしい気持ちもありますが、とても楽しかったです。

来年は懇親会でみんなで酒を飲みたいな!!!!

Vue3のSuspenseを使ってみた

Vue3のSuspenseについて興味があったので、試しに触ってみた内容をまとめます

Suspenseって?

非同期処理が解決されるまで、コンポーネントの代わりにフォールバックコンテンツをレンダリングする特別なコンポーネントです。 今まで、computedで変数を定義して、v-ifで表示制御していたのを簡単に書けるようにしたもののようです。

実際に書いてみた

親コンポネ

親コンポネでは以下のように書きます。

<template>
  <Suspense>
    <template #default>
      <ArticleList/>
    </template>
    <template #fallback>
      Loading...
    </template>
  </Suspense>
</template>

<script lang="ts">
import {defineComponent} from 'vue'
import ArticleList from "./components/ArticleList.vue"

export default defineComponent({
  components: {
    ArticleList
  },
})
</script>

Suspenseは、2つのスロットを持っています。

default

最終的にレンダリングするコンテンツ

fallback

defaultに定義したコンテンツの非同期処理が完了するまでのコンテンツ


今回作ったサンプルだと、子コンポネの非同期処理が終わるまではLoadingと表示されます。

子コンポネ

非同期処理を行う子コンポネでは、以下のようにかきます。

<template>
  <div class="card-wrapper">
    <div class="card" v-for="(article, key) in articles" :key="key">
      <h1>{{ article.title }}</h1>
      <div>{{ article.content }}</div>
    </div>
  </div>
</template>

<script lang="ts">
import {defineComponent} from "vue";

export default defineComponent({
  async setup() {
    const sampleArticles = [
      {title: '記事A', content: '記事Aの内容'},
      {title: '記事B', content: '記事Bの内容'},
      {title: '記事C', content: '記事Cの内容'},
    ]

    const fetchArticles = () => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(sampleArticles)
        }, 3000)
      })
    };
    const articles = await fetchArticles();

    return {
      articles
    }
  }
});
</script>

通常ではAPIから何らかのデータを取得して、取得したデータを表示すると思いますが 今回は、簡潔にするためにsetTimeoutで擬似的に非同期処理にしています。 ブログの記事一覧を引っ張ってくるAPIを叩いているイメージで書いています。


このように書くことで非同期処理が終わるまではLoadingと表示され、終わったら記事が表示されるようになります。 f:id:ryonnsui1201:20210329004426g:plain

エラーが発生した際のハンドリング

非同期処理が失敗することもあると思います。
その場合は、onErrorCapturedでエラーを補足し、エラーを表示します。
onErrorCapturedは子孫コンポーネントからエラーが捕捉されるときに呼び出されるライフサイクルフックです。

<template>
  <div class="card-list">
    <div v-if="error">
      {{ error }}
    </div>
    <Suspense v-else>
      <template #default>
        <ArticleList/>
      </template>
      <template #fallback>
        Loading...
      </template>
    </Suspense>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, onErrorCaptured } from 'vue'
import ArticleList from "./components/ArticleList.vue"

export default defineComponent({
  components: {
    ArticleList
  },
  setup(){
    const error = ref(null);

    onErrorCaptured((e) => {
      error.value = e
      return true;
    });

    return {
      error
    }
  }
})
</script>


f:id:ryonnsui1201:20210329011320g:plain

まとめ

以上、Suspenseの使い方でした。 とてもシンプルに非同期処理の際の表示処理を書けるので便利ですね。

参考

Suspense - new feature in Vue 3 - Vue.js Tutorials

ふりかえりの設計からファシリを最近2回したのでふりかえる(熱気球とStory of Storyやった)

f:id:skmtko:20210318185506p:plain

この記事は個人ブログと同じ内容です

sakamotoko.hatenablog.jp


agent bank開発チームのさかもとです。タイトルの通り、最近やったふりかえりに関して書きます!

話したいこと

先週と先々週にそれぞれ毛色の違ったチームふりかえりを行った。
さらにその2つのふりかえりに対してふりかえりの設計と、ファシリテーションを担当したので、そのことについて個人的に整理をする。

やったこと、わかったことを整理してみる。

本題

やったこと(大まかに)

まず、「毛色の違ったチームふりかえりって何やねん」ですが、

  • ① 3名で約1ヶ月分くらいのふりかえり
  • ② 8名で1スプリント(1週間)のふりかえり

を実施しました。

各ふりかえりの前には、「ふりかえりの後にどうなれていたらよいか」を上げるなどして、ふりかえりの手法の選定と、ふりかえりを実施する場所を準備するなどした。
ふりかえりの当日には、ファシリテーター役として、ふりかえりの進行を行った。

① 3名で約1ヶ月分くらいのふりかえり

自分を含めた開発チームのメンバーx2, 副業メンバーx1 の3名で行った。
どういう集まりかとういうと、約1ヶ月ほど前から、副業メンバーにジョインいただき、プロダクトのe2eテスト導入を進めてもらっているので、それに関わっているメンバーの集まりだった。

以下のような属性を含むので、さすがにふりかえりちゃんとしたほうがいいでしょうと思い、ふりかえりを計画 - 新しい試み - 新しくジョインしたメンバー - 副業メンバー - なかなか複雑なプロダクト

ふりかえりは、「熱気球」という手法をメインに取り入れて行った。

② 8名で1スプリント(1週間)のふりかえり

自分のいるagent bank開発チームではスクラム開発を採用しているので、スプリント(うちでは1スプリント=1週間でやってる)の節目に、次のスプリントをより良くするためにという目的で、ふりかえりを行っている。

最近は、「KPTA」という手法を用いてふりかえりを行っているが、この前の回のときに「他の手法でもやりたい!」と自分が発言したのがきっかけで、この回ではいつもとはいつもとは違う手法でやってみることになった。
結果的に「Story of Story」をやってみた。

やったことと、わかったこと(詳細に)

2回のふりかえりでやったことをそれぞれ説明する

① 3名で約1ヶ月分くらいのふりかえり

上に書いていたものを再度整理する。こんな感じのチームでのふりかえり。

  • 自分を含めた開発チームのメンバーx2, 副業メンバーx1 の3名
  • プロジェクトとして
    • 新しい試み
    • 新しくジョインしたメンバー
    • 副業メンバー
    • なかなか複雑なプロダクト
  • メンバージョインから1ヶ月たった

やったこと

このふりかえりの後にどうなっていたいか?を出す(ふりかえりの目的)

スクラムマスター(以降SM)の協力を得て、自分を含めた開発チームのメンバーx2 + SMの3人でふりかえりの設計を行った。
まず、手法を選定するために、ふりかえる期間にどんな事があって、ふりかえりをした結果、我々がどういい状態になっていたいかを出しあった。

f:id:skmtko:20210318114017p:plain

割と、コミュニケーションや、お互いの状況把握の点に関して少し改善の余地がありそうな印象だったので、上のようなアイデアが出た。

これをベースに、どんな手法を採用するかを考えていった。

ふりかえり手法の選定

ふりかえりチートシートや、https://www.funretrospectives.com/ を参考に、ふりかえり手法を選定していった。 結果的には、「何度かやったことがある」「楽しい」を理由に熱気球を採用することになった。あとで書くが、結果楽しかった。

参考: ふりかえりチートシート qiita.com

参考: 熱気球 blog.engineer.adways.net

ふりかえりの構成を考える

手法の選定も、ふりかえりの構成にはいるだろうなあとは思うけど、細かいことは気にしない。

大きな構成としては、以下のようになる

  • 場の設定 
  • データ収集(熱気球に決定)
  • イデア出し(熱気球に決定)
  • 次やることの決定
  • ふりかえりのふりかえり

それぞれどのようにすすめるかを決めていった。

ふりかえりの目的として、「現状の進捗把握」も上がっていたので、熱気球の前に現状把握のステップを追加した。

結果として、 以下のような構成になった

  • 場の設定 → ハピネスレーダー(顔文字でこの1ヶ月を表現)
  • データ収集① → 現状把握
  • データ収集②、アイデア出し → 熱気球
  • 次やることの決定 → 熱気球で出たアイデアから、投票などして決める(その場の状況次第)
  • ふりかえりのふりかえり → 雑談形式

MTGを設定していた時間が1時間だったので、それに収まりきれうように、書くステップに必要な時間を割り振ることまでした

事前にデータ収集

各ステップの時間を設定したところ、「現状把握の時間」そんなに時間取れないな、というのが判明した & おそらく0からやると半端なく時間がかかるのが予想された。
解決策としては、「前もって聞けば分かるっしょ」ということで、slackで事前に質問して回答をもらっておいた。

結果聞いててよかった、ふりかえりの時間では読み合わせて認識合わせる&わからんこと質問する に集中することができて時間を効率よく進めれた。

ふりかえりに必要な物理的な場を作る

以下のツールを使用してふりかえりを行うことを決定した

Miroには、ふりかえり用のボードを用意しその中に、各ステップでやること、必要な時間、熱気球のイラストを事前に設置して、スムーズに始めれるように準備した。

ここまでしてやっとふりかえりの準備 DONE

実際にふりかえりを実施

SMにファシリテーターをお願いしたかったがきれいに予定がかぶっていたので、自分がファシリテーターをしつつ、ちょこちょこふりかえりにも参加するのをやった。

ちなみに、熱気球やった形跡はこんな感じになりましたf:id:skmtko:20210318124420p:plain

次やることの決定で、ほぼすべてのアイデアを次やることにした

出たアイデアの実施難易度が低いものが割と多かったので、結果的に出たアイデア全てに対して、具体的にどういうアクションが取れるかを決めて、次やることとして整理できた。 アイデア整理などして、5個の次やるアクションが出せた。

10分オーバー

1時間のMTGであったが、すべて完了するまでに10分オーバーしてしまった。今回は、幸い副業メンバーの方の後の予定が空いていたため、最後まで全員参加で、ふりかえりのふりかえりまで実施できた。

わかったこと

少人数だとスムーズに進む

いつもやっているチームのスプリントふりかえりだと、だいたい5-8名ほどで実施してるが、今回は3名だけだったので、凄くスムーズに進んだ気がする。

というのも、話す人少ないので、必然的に参加者が自発的に発言しやすい状態になっていたのかもしれない。
また、大人数だと、全員分話し聞こうとして物量てきに時間食っちゃうみたいなことになる。

ハピネスレーダー面白い

場の設定に使ったハピネスレーダーが意外と面白かった。
ネガティブそうな顔文字を貼っている人がいて不安になりながら話を聞いてみると、「なるほど、そんなことあったんか〜」みたいな予想外の展開あった。

貼られたのはこれで、「ヤバイドウシヨウ」ってなった。 f:id:skmtko:20210318123443p:plain
聞いてみたら深刻な内容ではなかったので一安心した

ふりかえり始めての体験の人だったが、楽しく質の高い場にできた

副業メンバーが、今回やったようなふりかえりをやるのが、初めてとのことだったが、とても楽しかったとの感想を貰えることができた。

次回1.5ヶ月くらい立ってから再度やりたいねとなったので、ほんとにちゃんと準備かいがありました。
よかったーーーーー


8名で1スプリント(1週間)のふりかえり

上の熱気球の文でだいぶ、力尽きた感あるので、こっちはシンプルな文章になりそう...

やったこと

いつもと違うふりかえり試したい発言

この回の前の回のふりかえりのふりかえり最後のSMの「なんにかやってみたいことあります?」問に対して「いつも違う(KPTAじゃない)ふりかえり手法でやってみたい」と発言したことにより、今回設計からファシリまでを務めることになりました。

SMの支援を受けながら進めた。

ふりかえり手法の選定

まず、ここでもふりかえりチートなどを参考に、どんな手法を取るかというのを考えていきました。

毎日の終了時に夕会としてその日あったことや次何やるかを、YWTを使って簡単に振り返ってます。

こんな感じ f:id:skmtko:20210318151935p:plain

縦長の四角が一日ごとのフレームでその中で上から「Yやったこと」「Wわかったこと」「Tつぎやること」をためています 付箋の色は、赤→ポジティブ、黄→ニュートラル、青→ネガティブ を表現してもらってます

このスプリントでは、新しくジョインしたメンバーが開発に本格的に参加し始めた & メンターと一緒に進めている様子がなかなかうまくいっているようだったので、良い学びが多かったのでは、YWTの結果もポジティブな付箋多めかな?という肌感だった。

良い学びを伸ばす系のふりかえりにしてみようというのと、毎日のYWTの内容を生かしてできないかなということで、Story of Storyを採用してみることにした

ちなみにふりかえりチートシートには、こんなふうに書いてた

f:id:skmtko:20210318170112p:plain 「良いところを伸ばす◎」なるほど良さそう。

Story of Story の参考webでみっけたの英語のページしかなかった...

www.funretrospectives.com

ふりかえりの構成を考える

結果的に以下のような構成に落ち着いた

  • 場の設定 → ハピネスレーダー(①でやったときに楽しかったので、味をしめたため)
  • データ収集① → 毎日ためてたYWTのYだけさらっと眺める
  • データ収集② → Story of Story
  • イデア出し → Story of Storyで集めた
  • 次やることの決定 → 出たアイデアから、Effort x Value という手法を使って、絞り込み、やることの具体案を決める話をする
  • ふりかえりのふりかえり → 3人くらいに話してもらう

次やることの決定に関しては、いつもは投票によって、アクションを決める対象を絞り込んでいるが、ここもチャレンジしようということで、Effort x Value という手法を取り入れてみた。 簡単に説明すると、Effort(必要な努力)と Value(得られる価値)の2軸のマトリックスに対して、アイデアマッピングする。より少ない努力で得られる価値の大きいアイデアに対してアクションを考えるというもの。
マッピングする中で、どうやったら少ない努力にできるかとか、アイデアの共通認識を合わせる的なやつです

www.funretrospectives.com

ふりかえりに必要な物理的な場を作る

Miroにこんな感じのボードをつくった、 f:id:skmtko:20210318173310p:plain

会話はDiscordで音声チャットをしながらおこなう。 また、①のときと同様にスッテプで何をするのかと、各ステップでどのくらい時間を使うかを見積もって記した。

実際にふりかえりを実施

実際にStory of Storyやった形跡はこんな感じになった

f:id:skmtko:20210318173938p:plain

わかったこと

ふりかえり慣れている人達がやっても、ハピネスレーダー楽しい

前回味をしめて、今回もやってみたがやっぱり楽しかった。 これからも困ったらこれやりそう

Story of Story 結構時間かかる

ステップとして、以下があるが、なかなかステップが多かったのと、すべてのステップで少しづつ予定の時間をオーバーしてた。ざっくり2倍づつくらい。

  • 期間の間に起きたことを書き出すステップ(以下を書き出す)
    • 事実
    • コミュニケーション・コラボレーション
    • 続けること
    • 避けること・やめること
  • 書き出したものを皆で共有する
  • 上で書き出したものから以下と書き出す
    • 個人の学び・成長
    • チームの学び・変化

事実の付箋がとても大量に書き出された事により、各ステップで、全部を見ながら次の付箋を貼るのがかなり負担だったみたい。 起きた出来事が多かったり、参加者が多いふりかえりに使う場合は、注意が必要かなあとおもった。 もしくは、テーマを絞るとかしても良かったのかな?

プロブレムに焦点が当たりづらかった

毎回やっているKPTAに比べると、プロブレム(課題)に対して焦点が当たりづらかあったという感想があった。
確かに、割と良いコミュニケーションや、良い学びに対して焦点が行きがちで、「このモヤモヤに対して話そうぜ」てきな雰囲気に行きづらかった感がある。

また、データの量が多すぎてアイデア出すのが大変みたいな感想もあった。

Effort x Value の使い方工夫必要そう

イデアの絞り込みにEffort x Value を使ってみたが、これもプロブレムに焦点が行きづらい要因だったかも。 プロブレムの解決って、大体すごいパワー必要そうなことが多い気がするので...

なので、ここぞというときに使えるように使い所を探ってみようと思う。

大人数疲れる!

6人+ファシリ(俺)+SM(俺の支援)みたいな感じだったんですが、みんなの出したアイデアを全部みたり、話を深ぼったりすると単純に時間がかかるし、超パワーがいりますね。

まとめ

2つの毛色のちがうふりかえりの設計から、ファシリまでやってみたんですが、 人数や、期間、目的によって適したやり方全く違うんだなと、実感した。

手法によって、得意不得意があるっぽいので、どんな手法がどんなときに効果的かみたいなことを実践で覚えていきたいなとおもった。
また、既存の手法も目的によってカスタマイズしながら進めれるようになりたい。

オンライン、オフライン関係なく少人数のほうが進めやすいんだろうなとは思うが、オフラインだと、途中で少人数に分離して合流的なことするにも、ツールとかの準備必要でめんどいなあ。ガッツリやるときは、zoomとかGoogleMeetのブレイクアウトセッション準備したりなんしたりするのだろうなあ...

最後に定期的に行っているスプリントのふりかえりで、実験的な事ができたのは、チームとしてとても良かったなと思う。
引き続きチャレンジしていきたい!

AWS Aurora Backtrack(バックトラック)で DB データを特定日時の時点に巻き戻す

この記事は個人ブログと同じ内容です

www.ritolab.com


AWS が提供しているリレーショナルデータベース Amazon Aurora には Backtrack(バックトラック)という機能があり、これを用いると現在のインスタンス上で特定の時点へのデータの巻き戻しを行なう事ができます。

今回はこの Backtrack を使って Aurora のデータを特定時点へ戻したり進めたりしてみたいと思います。

Aurora Backtrack

Aurora のデータをコンソール上から復元する際に、スナップショットからのリカバリやポイントインタイムリカバリは新しい DB インスタンスを作成するためアプリケーション等、参照している側にもエンドポイントの変更が必要になる一方で、バックトラックは現在の DB クラスタをそのまま特定の時刻まで巻き戻すため、比較的容易に行えるのが利点。

docs.aws.amazon.com

データベースへ変更を加えるたびに新しいログレコードが作成され、ログシーケンス番号 (LSN) が生成されます。巻き戻し機能を有効にすることで、LSN のストレージ用クラスターに FIFO バッファーがプロビジョニングされます。これにより、素早いアクセスと秒単位で測定されたリカバリ時間が利用できるようになります。

引用元:Amazon Aurora Backtrack – 時間を巻き戻す

Aurora DB クラスタの作成

まずは AWS コンソール画面から、Aurora インスンタンスを作成します。

f:id:ro9rito:20210312170436p:plain

バックトラックの設定項目は以下になっています。

f:id:ro9rito:20210312170453p:plain

ターゲットバックトラックウインドウ
何時間バックトラックを行えるようにするか。最大 72 時間で、どの時点まで巻き戻せるようにするかの設定です。

キャプチャの通り、ここでは 5 時間としました。

その他、基本的な設定項目はすべて最小構成で行っています。

Auroraクラスタを作成しました。

f:id:ro9rito:20210312170538p:plain

検証データについて

バックトラックを行うにあたり、1 分おきに insert したデータを作成しました。

f:id:ro9rito:20210312170605p:plain

表示されているのが全レコードです。このデータに対して Backtrack を行っていきたいと思います。

Backtrack を行う(データを巻き戻す)

AWS コンソール画面から実行します。対象のクラスタを選択して アクション → バックトラックを押下します。

f:id:ro9rito:20210312170731p:plain

DB クラスターのバックトラック画面に遷移するので、まずは 11:30:00 時点のデータまで巻き戻してみます。

f:id:ro9rito:20210312170753p:plain

復元可能な最新時刻っていうのは、巻き戻せる最も過去の時刻(=それ以上過去は指定できない)です。

実行後はクラスタの詳細画面に遷移しますが、上部にバックトラックを実行している旨のメッセージが表示されています。

f:id:ro9rito:20210312170812p:plain

バックトラックが完了すると、以下にように「正常にバックトラックしました。」と表示されます。

f:id:ro9rito:20210312170829p:plain

データを確認してみます。

f:id:ro9rito:20210312170849p:plain

最新レコードが 11:29 のものになっています。しっかり 11:30:00 時点のデータに戻っていました。

redo 的なこともやってみる(巻き戻した時点から未来時間へデータを進める)

戻した時刻よりも未来の時刻を指定して、データを未来へ戻してみます。(未来へ戻すとかちょっとややこしい)

現在の 11:30:00 時点から 12:00:00 時点のデータへ進める事ができるか確認してみます。

f:id:ro9rito:20210312170934p:plain

バックトラック完了後、データを確認してみます。

f:id:ro9rito:20210312170953p:plain

12:00:00 時点のデータへ進められる事も確認できました。

最後に、用意したデータの最終レコードである 12:03:01 時点のデータに戻します。(12:00:00 -> 12:03:01)

f:id:ro9rito:20210312171011p:plain

こちらも問題なく、データを戻す事ができました。

f:id:ro9rito:20210312171030p:plain

ちなみに当然ながらバックトラック日時の設定の際は、現在日時よりも未来日時を指定するとしっかり怒られます。

f:id:ro9rito:20210312171043p:plain

まとめ

バックトラックを 3 回行いましたが、開始してから完了までの時間は、いずれも 1 分 10 秒ほどでした。(変更レコード量によってここの時間は変動するのかは気になるところ)

料金については、変更レコードの保存量 × 保持時間で掛かるようです。東京リージョンでは時間あたりの料金が 0.014USD/変更レコード100万件 となっていました。(2021/03/06現在)

aws.amazon.com

操作を誤りあるレコードを削除してしまったとか、バッチが失敗してデータが中途半端な状態になってしまった等の時には役立つなと感じました。

Backtrack 普段は出番のない機能ですが、いざという時に役に立つとても良い機能でした。

Kubernetesを学習するにあたって出てきた用語をまとめてみる

はじめに

agent bank開発を行っている北原です。

目的

  • 個人でKubernetes(以下k8s)を学習したときに関連用語がたくさん出てきて今現在ほとんど頭から抜けているため復習がてら用語をブログにまとめてみる

概要

  • ここでは、k8sについての概要説明等は、いたしません。以下をご参考ください。

kubernetes.io

k8sを構成する要素の概要と言葉

kubectl

  • K8s クラスタを操作するためのコマンド
  • Macならbrew install kubectlで入ります

コントロールプレーンコンポーネント

kube-apiserver

  • API サーバーは kubectl などの API クライアントからの REST リクエストを検証して API オブジェクトを構成、または、状態を報告する

kube-scheduler

  • ワークロード専用のスケジュール機能である

Kubernetesのスケジューラー | Kubernetes

kube-controller-manager

  • 制御ループを使ってシステム状態を調整する。モニタリングした現在状態から希望状態への遷移を実行する

cloud-controller-manager

Kubernetesのコンポーネント | Kubernetes

etcd

  • K8s クラスタのすべての管理データは etcd で保存される。

kubelet

  • 各ノードで動作する。
  • ボッッドとコンテナの実行
  • ポッドとノードの状態を API サーバーへ報告する
  • コンテナを検査するブローブを実行
  • 内臓する cAdvisor がメトリックスを集約して公開する
  • k8sが作成したものではないコンテナは管理しません。

kube-proxy

  • 各ノードで動作し、高可用性かつ低オーバーヘッドのロードバランシングを提供
  • サービスとポッドの変更を API サーバーで監視し、構成を最新状態に保ち、ポッド間とノード間の通信を確実にする
  • サービスの生成時に ClusterIP へのパケットをトラップして、対応するポッドへリダイレクトするように、iptables のルールを操作する
  • サービス名と ClusterIP をアドオンの DNS へ登録する

coredns

  • ポッドがサービス名から IP アドレスを得るために利用されている

kube-flannel

  • すべてのノードで実行され、複数のノードの間で IPv4 ネットワークを提供する。これによりコンテナ(ノード)は K8s 内部の IP アドレスでノードを超えて、疎通できるようになる
  • ネットワークポリシーを必要とする場合には、calico を使用しなければならない

calico-kube-controllers

  • calico のためのコントローラ。データストアとしての etcd を利用するために使われる

calico-node

  • 全てのノードで実行され、ノード間のコンテナ(ポッド)の疎通、アクセスコントロール、ルーティングを提供

kubernetes-dashboard

  • WebUI

metrics-server

  • heapster に代わり API の aggregation layer を通じて、K8s クラスタ全体のメトリクスを収集する

k8sを触る際によく出る言葉や概要

コンテナ

  • 必ずポッド内で実行する
  • 軌道に設定できる項目がある

ポッド

  • コンテナを実行するためのオブジェクトで、複数のコンテナを内包している

コントローラ

  • ポッドの実行を制御するオブジェクト

コンフィグレーション

  • コンテナ内のアプリケーションの設定やパスワードなどの情報は、デプロイされた「名前空間」から取得することが推奨されている。
  • 設定を保存する:ConfigMap(コンフィグマップ)
  • 秘匿情報を保存する:Secret(シークレット)
  • 名前空間下に保存された情報は、コンテナ内のファイルや環境変数として、アプリケーションのコードから参照できるようになる

k8sの状態を示す言葉と概要

ContainerCreating

  • イメージをダウンロード中、またはコンテナ起動進行中を表す。
  • ConfigMap や Secret をマウントできず、コンテナ生成が保留された時もこの値が表示される

CrashLoopBackOff

  • ボッド内のコンテナが終了し、次の起動まで待機状態になる。
  • コンテナ内のプロセスを見直す必要がある

Pending

  • ポッド生成の要求を受け取るが、1 つ以上のコンテナが作成されていない状態
  • リソース不足、ポリシー制約によってスケジュールできていないケースがあるため見直す

Running

  • ノードに対応づけられ、少なくとも 1 つのコンテナが実行中、開始中、または再起動中である

Terminating

  • コンテナへの終了要求シグナルが送られ、コンテナが終了するまで待機中
  • 猶予時間をすぎ、コンテナが終了できていない場合、強制終了する

Succeeded

  • 正常

Completed

  • ポッド内のコンテナが正常終了し存在している。削除されるまで存在し続ける。ポッド名を指定することでログやステータスを取得できる
  • ポッド内に複数のコンテナがある場合、第 1 コンテナが正常終了するとポッドは、正常終了として扱われる

Error

  • コンテナが異常終了した。
  • Completed の対義

Failed

  • ポッドないの少なくとも 1 つのコンテナが異常終了した

Unknown

  • 何らかの理由により、ポッドの状態を取得できない状態
  • また、ノード障害、マスターからノードの状態を取得できなくなったときにもこの表示になる

参考資料

kubernetes.io

Amazon ECS でタスクをスケジューリングして定期的に実行する

この記事は個人ブログと同じ内容です

www.ritolab.com


Amazon ECS には タスクをスケジューリングして動作させることのできる機能があり、これを用いることで毎日走らせたい処理など定期的に行いたい処理を実行する事ができます。

今回は AWS ECS の「タスクのスケジューリング」を使って、タスクを定期的に実行してみます。

ECS タスクのスケジューリング

ECS の画面から設定が可能ですが、内部的には CloudWatch Events Rule を作成してスケジュールを構成しているようですね。

Amazon ECS タスクのスケジューリング

docs.aws.amazon.com

ECS の構築

まずはベースとなるタスク定義やクラスタを作成しておきます。

  • Terraform (v0.14.3) で行います。
  • 起動タイプは FARGATE です。
  • コンテナのイメージは ECR に登録済みの前提です。

IAM の作成

ECS でタスクを実行する IAM Role 作成します。

main.tf

# IAM Role - ECS Task Execution for Scheduler
resource "aws_iam_role" "ecs_scheduler_task_execution" {
  name               = "EcsTaskExecutionRole-sample"
  assume_role_policy = file("policies/iam_role/ecs_task_execution.json")
}
# IAM Role Policy - ECS Task Execution for Scheduler
resource "aws_iam_policy" "ecs_scheduler_task_execution" {
  name        = "EcsTaskExecutionPolicy-sample"
  description = "Ecs Task Execution"
  policy      = file("policies/iam_policy/ecs_task_execution.json")
}
# Attach Policy to Role / Scheduler
resource "aws_iam_role_policy_attachment" "ecs_scheduler_task_execution" {
  role       = aws_iam_role.ecs_scheduler_task_execution.name
  policy_arn = aws_iam_policy.ecs_scheduler_task_execution.arn
}

読み込んでいる各ファイルの内容は以下です

policies/iam_role/ecs_task_execution.json

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "ecs-tasks.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

policies/iam_policy/ecs_task_execution.json

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "ecs-tasks.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

タスク定義・クラスタ作成

ECS のタスク定義・クラスタを作成します。

main.tf

# CloudWatch Logs - log group
resource "aws_cloudwatch_log_group" "ecs_scheduler" {
  name = "/ecs-scheduler"
}

# Task Definition
resource "aws_ecs_task_definition" "task_scheduler" {
  family                   = "scheduler"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = "256"
  memory                   = "512"
  container_definitions    = templatefile("task-definitions/scheduler.json", {
    log_group_name      = aws_cloudwatch_log_group.ecs_scheduler.name
  })
  execution_role_arn       = aws_iam_role.ecs_scheduler_task_execution.arn
}

# ECS Cluster
resource "aws_ecs_cluster" "task_scheduler" {
  name = "scheduler-cluster"
}

読み込んでいる各ファイルの内容は以下です

[
    {
        "name": "<< CONTAINER-NAME >>",
        "image": "<< AWS-ID >>.dkr.ecr.<< REGION >>.amazonaws.com/<< CONTAINER-IMAGE-NAME >>:<< CONTAINER-IMAGE-TAG >>",
        "cpu": 128,
        "memory": null,
        "memoryReservation": 128,
        "logConfiguration": {
            "logDriver": "awslogs",
            "options": {
                "awslogs-group": "${log_group_name}",
                "awslogs-region": "<< REGION >>",
                "awslogs-stream-prefix": "scheduler",
                "awslogs-datetime-format": "%Y-%m-%d %H:%M:%S"
            }
        }
    }
]

<< ... >> としている部分は各々で必要な値が入ります。

これでベースとなる ECS 環境が構築できました。

タスクのスケジューリング設定

ここから ECS のタスクスケジューリングを設定していきます。

Cloudwatch Events を設定することでタスクスケジューリングを実現します。

IAM Role の作成

CloudWatch Events の IAM Role を作成します。

main.tf

# IAM Role - ECS Events
resource "aws_iam_role" "ecs_events" {
  name               = "EcsEventsRole-sample"
  assume_role_policy = file("policies/iam_role/ecs_events.json")
}
# IAM Role Policy
resource "aws_iam_policy" "ecs_events" {
  name   = "EcsEventsPolicy-sample"
  policy = templatefile("policies/iam_policy/ecs_events.json", {
    // リビジョンは固定しない
    task_definition_arn = replace(aws_ecs_task_definition.task_scheduler.arn, "/:\\d+$/", "")
  })
}
# Attach Policy to Role
resource "aws_iam_role_policy_attachment" "ecs_events" {
  policy_arn = aws_iam_policy.ecs_events.arn
  role       = aws_iam_role.ecs_events.name
}

1 点だけ注意するポイントがあります。IAM Policy 作成時のポリシー定義で、ECS タスク定義の ARN を指定しますが、この値はリビジョンを除いたものを渡します。

リビジョンが指定されたままの ARN で ポリシーを作成してしまうと当然ながらそのリビジョンでのみ実行可能なポリシーになってしまうため、例えばアプリケーションの新たなリリースを行ってイメージを更新した(イメージのタグを更新した、など)際にはタスク定義のリビジョンが一つ上がるので、作成したポリシーではスケジューリング実行ができなくなってしまいます。

他、読み込んでいる各ファイルの内容は以下です。

policies/iam_role/ecs_events.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "events.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

policies/iam_policy/ecs_events.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "ecs:RunTask",
            "Resource": "${task_definition_arn}"
        }
    ]
}

タスクスケジューリング

スケジュールを設定します。

main.tf

# CloudWatch Event Rule
resource "aws_cloudwatch_event_rule" "ecs_scheduled_task" {
  name                = "ecs-scheduled-task"
  schedule_expression = "cron(20 2 * * ? *)"
}

# CloudWatch Event Target
resource "aws_cloudwatch_event_target" "ecs_scheduled_task" {
  arn       = aws_ecs_cluster.task_scheduler.arn
  rule      = aws_cloudwatch_event_rule.ecs_scheduled_task.name
  role_arn  = aws_iam_role.ecs_events.arn
  target_id = "scheduler-target"
  input     = file("container-overrides/ecs_scheduled_task.json")

  ecs_target {
    // リビジョンなしで渡すことで常に最新のバージョンを使用するようにする
    task_definition_arn = replace(aws_ecs_task_definition.task_scheduler.arn, "/:\\d+$/", "")
    task_count          = 1
    launch_type         = "FARGATE"
    platform_version    = "1.4.0"

    network_configuration {
      subnets          = [aws_subnet.private_1.id,aws_subnet.private_2.id]
      assign_public_ip = false
    }
  }
}

以下、いくつかポイントがあります。

スケジュールの設定

CloudWatch Event Rule の設定において schedule_expression を指定していますが、ここでどういったスケジュールで動作させるのかを指定します。

上記のような cron 式、または rate 式で記述できます。

ルールのスケジュール式

docs.aws.amazon.com

1 つ注意なのが、 cron 式を使う場合 AWS 上では UTC で実行されるため、日本のタイムゾーンで考えると時刻指定は -9h で行う必要があります。

今回は、毎日 11:20 に実行されるように設定しました。

サブネットの指定

network_configuration の値について、ここではマルチ AZ かつプライベートサブネットに ECS を展開しているため subnets および assign_public_ip は上記のような指定になっています。

サブネットの指定などは自身の環境に合わせて設定を行ってください。

Resource: aws_cloudwatch_event_target
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target

ContainerOverride

読み込んでいる各ファイルの内容は以下です

container-overrides/ecs_scheduled_task.json

{
    "containerOverrides": [
        {
            "name": "<< CONTAINER-NAME >>",
            "command": ["php", "artisan", "sample:logger"]
        }
    ]
}

ここでは ContainerOverride の値を設定しています。コンテナ起動時のデフォルトコマンドがここで指定したもので上書きされます。

タスク起動時にここで指定したコマンドが実行されるイメージです。

ContainerOverride

docs.aws.amazon.com

今回はサンプルとして、PHP フレームワークである Laravel のコマンドを動かす想定として、artisan コマンドを記述しています。

コンテナ起動時のデフォルトコマンドを上書きしたことによって動作は以下のようになります。

  1. スケジューリングによって指定した時間にコンテナが起動する
  2. コマンド php artisan sample:logger を実行する
  3. 処理が終了したらタスクが終了する

動作確認

ECS でのタスクスケジューリングの設定が完了したので、AWS コンソール画面から確認してみます。

ECS クラスタの画面からタスクのスケジューリングタブを選択すると、スケジュールが設定されている事が確認できます。

f:id:ro9rito:20210215084907p:plain

また、CloudWatch Events の Rule を確認すると、指定の通りに毎日 11:20 にトリガーが設定されている事が確認できます。

f:id:ro9rito:20210215084926p:plain

時間になったらタスクが起動しました

f:id:ro9rito:20210215084948p:plain

タスクを実行した際にログを出力するようにしておいたのでそちらも確認してみます。

f:id:ro9rito:20210215085012p:plain

スケジューリングでのタスク実行が動作している事を確認できました。

まとめ

ECS のタスクスケジューリングを使う事で、タスク実行をスケジュール化できました。

定期的に実行するような処理は ECS のタスクのスケジューリングでいい感じに行えそうでした。