Quantcast
Channel: Cybozu Inside Out | サイボウズエンジニアのブログ
Viewing all 692 articles
Browse latest View live

QA勉強会支援チーム「ミネルヴァ」の紹介(2021年版)

$
0
0

こんにちは!開発本部kintoneチームのひとみです。食品開発者からQAエンジニアに転職して2年目です。

サイボウズは社内勉強会が盛んで、毎日何かしらの勉強会が開かれています。個人で開催しているものもありますが、勉強会開催を支援するチームもあります。今回は私も所属している、品質保証関係の勉強会開催を支援するチーム「ミネルヴァ」を紹介します。

立ち上げから今まで

2018年までの開発本部はロールごとに部が分かれており、QAエンジニアは品質保証部に所属していました。品質保証部内で勉強会開催を支援する目的で立ち上げられたチームが「ミネルヴァ」です。(詳しい経緯はこちら

2019年に開発本部から部がなくなり、開発本部の直下に各プロダクトの開発チームがフラットに並ぶ組織に変わりました。それぞれのチームにはプロダクトマネージャー、プログラマー、デザイナーなど開発に関わる全ロールのメンバーが所属します。QAエンジニアも各チームに配属されたので、品質保証部のようなQAエンジニアだけの組織はなくなりました。

品質保証部内で活動していたミネルヴァでしたが、組織改編後は、QAエンジニアに限らず品質保証に興味がある人へ学ぶ機会を提供する活動を続けています。

ミネルヴァは現在、開発本部・運用本部の各チームから集まった有志4名で活動しています。

活動内容

主な活動は以下の2点です。

1. QA入門講義の企画と運営

QAエンジニアとして入社された方に対しての講義です。本人や配属先のチームと相談しながら実施の有無・内容・スケジュールを決めて開催準備を行います。

担当製品に限らない知識を学ぶことができる上、所属チーム以外のメンバーと交流する機会にもなります。

2. 勉強会開催の支援

社内勉強会の開催支援活動です。 勉強会開催までのステップは以下の通りです。

  1. 話したい話題や聞きたい話題がある人が、kintoneのアプリに話題を登録する

  2. 開催希望者が日本・中国・ベトナムのいずれかで5名以上になった話題は、開催決定にする

  3. ミネルヴァが、社内メンバーへの講師依頼、開催日時の決定、Zoom含む会議室の確保、開催の告知を行う

  4. 講師が、資料の準備と当日の進行を行う

当日の勉強会の様子はこのような感じです。 勉強会の様子を写した写真

kintoneでテキストコミュニケーションをする場所(通称「実況スレ」)も用意します。勉強会中に参加者同士で意見交換をしたり、講師への質問を残したりする目的で使われています。 勉強会中に行われているテキストコミュニケーションの写真

昨年(2020年)は以下のような勉強会を開催しました。

ツールの紹介

  • Cucumber1とGauge2を触ってみた

社内の知見の共有

  • Claraチーム3で作ったKeepsの紹介

  • cybozu.comの構成4(QA入門講義として開催)

参加報告

  • JSTQB AL テストアナリスト合格体験記

  • SQiP研究会ってなあに?

  • WACATE2019冬 参加報告5

ワークショップ

  • Agile Testing Days 動画鑑賞会6

  • WACATE2019冬のステップアップワークをやってみる

参加者の声

過去の勉強会に参加したり、資料を参照したりしたメンバーからの声をご紹介します。

  • 品質保証部がなくなっても、QAエンジニアのつながりを感じられるのでありがたい

  • 新メンバー向けの研修内容を考えていたが、ミネルヴァの資料が充実しているので何も用意する必要がなかった

  • 開催告知などをお任せできるので、気軽な気持ちで勉強会を開催できる

さいごに

今回は品質保証関係の勉強会開催を支援するチーム「ミネルヴァ」を紹介しました。 引き続き、品質保証に関する知識や経験を気軽に共有できる環境を整えていきます!

サイボウズではQAエンジニアの新卒・キャリア採用を行っています。

あなたも一緒に、サイボウズでQAエンジニアとして働きませんか?


  1. https://cucumber.io/

  2. https://gauge.org/

  3. US版Kintoneの販売管理システム

  4. サイボウズ製品のインフラ基盤の構成

  5. この社内勉強会では社外からも参加者を募り、希望してくださった2名に参加していただきました。サイボウズの品質保証の知見をより広く共有するとともに、交流・認知強化、採用などにもつなげたい、という思いがありました。

  6. https://youtu.be/FIJQPHNS5Jc


プロダクト支援チームでkintoneのStorybookをホスティングした話

$
0
0

こんにちは。生産性向上チーム&フロントエンドエキスパートチームです。

今回は私たちプロダクト支援チームが普段どのようにプロダクトチームを支援しているかの一例として、kintoneのStorybookを社内からいつでも確認できるようにホスティングするまでの流れを紹介します。

支援チームとプロダクトチームの関わり

まずは私たち支援チームとプロダクトチームが社内でどのように関わり、開発しているのかを紹介します。

サイボウズ開発組織図

プロダクト支援チームはプロダクトチームに属しておらず、さまざまなプロダクトの課題解決のために動いています。 例えば、プロダクトチームが手の回っていない最新技術への追従や開発体験の改善を行っています。

生産性向上チームとフロントエンドエキスパートチームの詳細については、それぞれ以下の紹介記事を参照してください。

今回はその支援のひとつとして、プロダクトチームでStorybookを社内からいつでも確認できるようにホスティングしたいというIssueがあったので、一緒に改善しました。

kintone開発におけるStorybookの活用

Storybookとは、UIコンポーネント一覧をカタログのように表示できるようにして、開発チーム内でUI開発のコミュニケーションをとりやすくするツールです。

Storybookの解説

画像引用元:https://storybook.js.org/docs/react/get-started/introduction

現在、kintoneチームではClosure Toolsで作られているのをReactに置き換えています。
この置き換えにデザイナー、開発者、PMなど多くの関係者が関わっており、実装されているUIの動作やスタイルを視覚的に確認するために、Storybookを活用しています。

しかし、改善前はStorybookを確認するときに以下のような課題がありました。

  • 確認のたびに開発環境を立ち上げないといけない
  • PMやデザイナーと一緒に確認するときに、開発者しか手元で見れない
  • 開発者以外に環境を作ってもらうのはハードルが高い

開発チームではこの課題を認識はしていたものの、機能開発をしている中で優先度が落ちており、手が付けられていない状態が続いていました。
そこでプロダクト支援チームである私たちがこの課題を解決するため、社内からいつでもStorybookを確認できるようにホスティングの仕組みを整えました。

Storybookをホスティングする方法

Storybookをホスティングする上で、以下のことを実現しようと考えました。

  • 社内環境からのみ閲覧できる
  • PRごとにStorybookを見られるURLが発行される

これを実現する方法はいくつもあり、私たちが検討した選択肢と、それらをなぜ採用したのか・しなかったのかを以下にまとめます。

GitHub Pages

kintoneチームはGitHub Enterprise Serverを使っているので、GitHub Pagesを使えば前提条件は満たせると考えました。

しかし、PRごとにホスティングしようとすると、同一リポジトリで複数ディレクトリにわける、もしくは別リポジトリに転送する必要があり、少し手間がかかりそうでした。
このため、今回は採用を見送りました。

社内インフラにホスティング

社内でVMなどを構築して、PRごとにそのVMに転送してホスティングするという方法も検討しました。

しかし、VMを構築するとなると、VM自体の運用や監視が必要になるため、運用コストが大きいと判断して見送りました。

Chromatic

ChromaticはStorybookのメンテナーによって作られたサービスです。
サービス内にStorybookを展開できるだけではなく、UI上でコメントのやりとりができたり、UIの差分テストを行うこともできます。

kintoneチームで使用しているのがGitHub Enterprise Serverだったため、Enterprise Planの契約が必要であり、手軽には使い始められなさそうということがわかりました。
今回やりたいことはStorybookをホスティングすることだけだったので、コストとやりたいことが見合ってないため見送りました。

いつか使ってみたいです。

AWS Amplify

AWS Amplifyを使うことで、静的なウェブサイトをホスティングできます。

AWS AmplifyでIPアドレス制限をかける方法を調査したところ、Amplify単体では実現できず、AWS WAFを組み合わせるのが一般的ということがわかりました。
やりたいことに対して構築が大掛かりすぎて、コストが見合ってないと判断し見送りました。

Netlify

Netlifyは静的ホスティングサービスです。
社内の一部ですでにNetlifyを使っており、GitHubと連携して手軽に静的ホスティングができます。

しかし、NetlifyはIPアドレスやSSOによるアクセス制限機能が見当たらなかったため、社内からのみ見られるようにしたいという今回の用途には合わず、採用を見送りました。

Amazon S3

S3は静的サイトをホスティングする機能があり、バケットポリシーでIPアドレス制限を設定できます。
すでに社内でAWSを使用しており、利用するための導入コストはほとんどかかりません。(参考:AWS + Azure ADによるSingle Sign-Onと複数AWSアカウント切り替えのしくみ作り - Cybozu Inside Out | サイボウズエンジニアのブログ

調査したところ、株式会社スタディストさんの記事で今回やりたいことがほぼ実現されており、仕組みの構築も簡単そうでした。

手軽に始められそうだったため、今回はS3で実現してみることにしました。

StorybookをS3に上げてIPアドレス制限をかける方法

S3バケットの作成

StorybookをホスティングするためのS3バケットを作成し、バケットポリシーでIPアドレス制限を設定します。

S3バケットの設定はTerraformで管理することにしました。ここではS3バケット名を仮に"hosting-storybook"として、以下のようなTerraform設定ファイルを作成しました。

resource "aws_s3_bucket" "hosting_storybook" {
  bucket = "hosting-storybook"
}

resource "aws_s3_bucket_policy" "hosting_storybook" {
  bucket = aws_s3_bucket.hosting_storybook.id

  policy = jsonencode({
    Version : "2012-10-17",
    Statement : [
      {
        Effect : "Allow",
        Principal : "*",
        Action : "s3:GetObject",
        Resource : "arn:aws:s3:::hosting-storybook/*",
        Condition : {
          IpAddress : {
            "aws:SourceIp" : [<社内のIPアドレス一覧>]
          },
          Bool : {
            "aws:SecureTransport" : "true" # https のみ許可する
          }
        }
      }
    ]
  })
}

これで作成したS3バケットにアップロードされた静的ファイルは、社内環境からのみアクセスできるようになります。

また、CIからこのS3バケットにStorybookの成果物をアップロードするためのIAMユーザーとポリシーを作成し、アクセスキーを発行しておきます。

CIの設定

今回はCircleCIでPRごとにStorybookの成果物をS3にアップロードするジョブを設定しました。
具体的なジョブの設定内容は以下のようになります。
CircleCIの環境変数に、AWSのアクセスキーIDとシークレットアクセスキー、GitHubのパーソナルアクセストークンがそれぞれ、AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYGITHUB_TOKENに設定されている想定です。

deploy-storybook:docker:- image:<Storybookのビルドができ、AWS CLIが入ったイメージ>
    environment:STORYBOOK_OUTDIR: /tmp/storybook-static # Storybookビルド時に成果物が保存されるディレクトリsteps:- run: npm ci
      - run:name: Build Storybook
          command:<Storybookビルドコマンド 参考:https://storybook.js.org/docs/react/workflows/publish-storybook>
      - run:name: Deploy Storybook to AWS S3
          command: |
            aws s3 sync $STORYBOOK_OUTDIR s3://hosting-storybook/$CIRCLE_BRANCH
      - run:name: Create commit status
          command: |
            STORYBOOK_URL=https://hosting-storybook.s3-ap-northeast-1.amazonaws.com/${CIRCLE_BRANCH}/index.html
            STORYBOOK_STATUS_BODY="{\"state\": \"success\", \"target_url\": \"$STORYBOOK_URL\", \"context\": \"storybook\", \"description\": \"このPRのStorybookを見るにはDetailsをクリック!\"}"
            GITHUB_API_END_POINT="https://<GitHubのパス>/api/v3/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/statuses/$CIRCLE_SHA1"
            wget --method=POST \
              --header="Authorization: token $GITHUB_TOKEN" \
              --header="Content-Type: application/json" \
              --body-data="$STORYBOOK_STATUS_BODY" \
              $GITHUB_API_END_POINT

このジョブをトピックブランチでコミットごとに実行するように設定しました。
この結果、コミットステータスにホスティングされたStorybookへのURLが保存されるようになり、以下の画像のようにPR上から簡単にStorybookを閲覧可能になりました。

GitHub上でのStorybookプレビュー

ハマったところ

今回はブランチごとにS3バケットのサブパスにStorybookをデプロイするため、Storybookで参照している画像などのメディアファイルを一緒にデプロイする場合、コードからファイルを参照するためには相対パスを設定する必要がありました。
最初は絶対パスで指定していたためアイコンが表示されない状態になり、トラブルシューティングが大変でした。(参考:Absolute versus relative paths

作っただけで終わりではない!

ここまでの内容で社内からいつでもStorybookを確認できるようにホスティングの仕組みを整えることができました。 ところが、実際に運用を進めていく上で次のような修正や改善を行いました。

利用者が求めているものとのギャップを修正

成果物ができたので実際に運用をするために、kintoneチームと一緒にデザインチームともMTGを行いました。
そこで上がった内容は、デザインチームがStorybookを確認するタイミングは、メインブランチにある最新状態のものだけで十分ということでした。
開発メンバーは手元でStorybookの確認ができるため、ブランチごとにStorybookの状態を確認する必要はないということに気がつきました。

この意見を踏まえて、各ブランチごとにStorybookのビルドとホスティングをしていたのを、メインブランチのみで行うようにジョブを修正しました。

実際に作ったものと利用者が求めてるものにギャップが発生してしまっていたため、こまめに関係者とコミュニケーションをとって調整することは重要だと実感しました。
ここは今回の反省ポイントです。

周知と全体への社内ドキュメント化

大抵、サイボウズではプロダクト支援チームとプロダクトチームはよくモブをして解決することが多いですが、今回はプロダクト支援チームで開発したため、プロダクトチームではなにが行われているか分からない状況でした。

そのため、一連の流れをZoomで説明して、録画も残しておきました。
録画を残すことで、今回の作業に入っていない人も情報を追うことができます。

その後、社内ドキュメントでも今回Storybookをホスティングした目的や仕組みを文書化しました。

プロダクト支援チームは開発するまでがゴールではなく、プロダクトチーム側で運用が回せるように支援を行っています。

おわりに

今回はプロダクト支援チームがプロダクトチームの課題を解決した例を紹介しました。
この記事で紹介したもの以外にも日々さまざまな課題に取り組んでいます。

私たちは一緒に課題を解決してくれる仲間を募集しています。

cybozu.co.jpcybozu.co.jp

興味がある方はTwitterでも受け付けています。
お気軽にご連絡ください。

  • 生産性向上チーム: @miyajan
  • フロントエンドエキスパートチーム: @sakito

フロントエンドエキスパートチーム内でスピードハッカソンを開催しました

$
0
0

フロントエンドエキスパートチーム内でスピードハッカソンを行いました。通常のスピードハッカソンは共通の題材に対してチームごとにスコアを競うというイメージですが、今回はランダムに決めたチームでそれぞれのチームごとに題材を探すところからはじめ、計測方法も自由としました。

今回のハッカソンは、チームのコミュニケーション促進を主な目的として開催しましたが、結果としてプロダクトの改善につながる成果、とっかかりとなる知見などが得られました。

今回取り組んだ内容などを紹介します。

開催目的とチーム分け

フロントエンドエキスパートチームは現在 9 名いますが、メンバーごとに異なるプロダクトや関心事を業務としています。そのため、同じチームに在籍していながら接点が少ないメンバー同士も存在しています。

普段接点が少ないメンバー同士でチームを組んで、コミュケーションを取りながら一つのことに取り組める機会としてハッカソンが適していると考えて、今回スピードハッカソンの開催に至りました。

チーム分けは次のようになりました。

スピード王 🚀

チームスピード王(@__sakito__, @shisama_, @b4h0_c4t)です。

私達は React + TypeScript で構成されている Web アプリケーションフロントエンドのバンドルサイズ軽量化及び Lighthouse においてのスコア改善を実施しました。

バンドルサイズ軽量化

はじめに、 webpack-bundle-analyzer を用いてアプリケーションに含まれるモジュールが占める割合を可視化しました。

webpack v5 から webpack-cli に標準で webpack-bundle-analyzer が組み込まれたことと、今後の開発体験向上のために webpack v5 へのアップデートを視野に入れて調査しましたが、 webpack v5 で storybook が動作しない挙動に当たってしまい、今回は webpack のアップデートを断念して、webpack-bundle-analyzer を別途インストールして利用することにしました。

f:id:cybozuinsideout:20210225110250p:plain
開始時点での webpack-bundle-analyzer 実行結果

初回の webpack-bundle-analyzer の実行結果では、 Moment.js の locale が全て読み込まれており、かなりファットな状態になっていました。 GoogleChromeLabs でも利用しない locale は削除することが推奨されているため、必要のない locale は webpack-plugin 等を用いて削除するのがよいでしょう。 今回は us locale 以外は全く使用されていなかったことから、それなりにバンドルサイズの軽量化が見込めました。

Moment.js の locale を削除する手法以外にも、 Moment.js を別のパッケージへ移行する手法もあります。 現在、 Moment.js はセキュリティパッチのみのアップデートで、他のパッケージへの移行が推奨されています。

今回次のような理由から Day.js への移行を実施することにしました。

  • インターフェースが Moment.js と似ているため置き換えのコストが低い
  • ファイルサイズが軽量

今回対象としたプロダクトが、Moment.js の機能をそれほど利用していなかったのも以降を決めた理由として挙げられます。

Day.js に置き換えたあとに再度バンドルサイズを比較したところ、約 300KB ほど軽量化できました。

f:id:cybozuinsideout:20210225110254p:plain
Day.js へ移行後の webpack-bundle-analyzer 実行結果

Moment.js 以外の気になる点として、本来本番ビルドに必要のない redux-logger がバンドルに含まれているのに気づきました。

これはロガーの読み込み処理を開発モードかどうかに関わらずimportで行っていたため、バンドルに含まれるようになっていました。これを開発モードかどうか判定する処理のあとにrequireで読み込むことで解決しました。

変更前のコードは次のようになります。

import{ createLogger } from "redux-logger";
// ...省略if (process.env.NODE_ENV !== "production") {//use create-logger}

これを次のように変更しました。

if (process.env.NODE_ENV !== "production") {const{ createLogger } = require("redux-logger");
  //use create-logger}

この変更によって redux-logger は本番ビルドには必要ないコードとして認識されるようになり、ビルド時のデッドコードエリミネーションによってバンドルに含まれないようになります。

変更を加えた結果、webpack-bundle-analyzer の出力は次のようになりました。

f:id:cybozuinsideout:20210225110300p:plain
redux-logger削除後の webpack-bundle-analyzer 実行結果

最終的に、プロダクト内 2 つのバンドルファイルにおいて、それぞれ 300-400KB 程度の軽量化に成功しました。

Lighthouse のスコア改善

レイアウトシフトの改善のため、どのような画像が利用されているのか自明な DOM に対してデフォルトの width と height を設定する対応をしました。細々とした作業ではありましたが、レイアウトシフトはフロントエンドのパフォーマンスにおいて重要な指標の一つです。

この対応で、Lighthouse のパフォーマンス項目において全ての要素がグリーンとなりました。

パフォーマンスについては元々のスコアが高かったですが、そこから少しでもスコアを向上させることができたため十分な成果と感じています。

f:id:cybozuinsideout:20210225092104p:plain
初期の Lighthouse スコア

f:id:cybozuinsideout:20210225092109p:plain
レイアウトシフト対応後の Lighthouse スコア

チーム:坂東太郎 🍜

チーム坂東太郎(@koba04, @__sosukesuzuki, @nakajmg)です。

私達のチームは、kintone のポータル画面のレイアウトシフトの改善に取り組みました。

レイアウトシフトが起きているのはポータルのカスタマイズで埋め込んでいる widget エリアと、スペースとアプリの一覧表示エリアです。いずれのエリアもページアクセス時に非同期で表示するコンテンツのデータを取得して、その後レンダリングするようになっています。データ取得の完了順やレンダリングによってレイアウトシフトが起きている状況です。

ぞれぞれのエリアのレンダリングまわりのコードを読み、原因と改善できそうな箇所を探しました。

改善策の検討と実施

レイアウトシフトに対して取れる基本的な手段として、コンテンツをレンダリングするエリアの幅と高さを事前に確保しておくことが挙げられます。

widget エリアやスペース一覧とアプリ一覧のいずれも、初期表示する最大件数がレンダリングする関数に引数として渡されていました。この最大件数をもとにコンテンツに必要なエリアを算出し、一時的に高さを確保しておく対応を行いました。

widget エリアは件数分の高さを確保するように、スペース一覧とアプリ一覧には Facebook で見られるようなコンテンツプレースホルダーの表示を試してみました。

f:id:cybozuinsideout:20210225092114p:plain
コンテンツプレースホルダー

これらの対応の結果レイアウトシフトが軽減され、最初に表示されていたコンテンツが下部に消えるような状態が改善できました。

変更前と変更後で Lighthouse でのスコア計測も行いましたが、非同期処理のタイミングからなのか、レイアウトシフトについてうまく評価できていないようでした。そのためスコア的には変化がありませんでした。スコアには変化が見られませんでしたが、体感としてページ表示の際のガタツキが大幅に軽減できていたので効果は出ていると感じました。

今回行った改善はポータルページのことだけを考えた場当たり的な対応でしたので、プロダクトにコードを反映するにはまだまだ検討する項目がありそうでした。しかしながら内部のコードをしっかりと読むことで、該当箇所の理解が深まったので、これを足がかりとして改善に繋げられそうです。

チーム:とまつオールスターズ 🍻

とまつオールスターズ(@toshi__toma, @pirosikick, @zaki___yama)です

私達のチームは、kintone のモバイルページのパフォーマンス改善に取り組みました。

Lighthouse でモバイルページのスコアを計測した結果から、次の箇所を改善できそうでした。

  • 画像周りの最適化
  • JavaScript の最適化
  • レイアウトシフトの改善

計測は Chrome の DevTools でモバイルの表示をエミュレートして行いました。

画像周りの最適化

ページ内の画像をすべて読み込むようになっていたので、loading="lazy"をつけて遅延読み込みするようにしました。合わせて、表示サイズが決まっている画像に width と height を指定して、レイアウトシフトが起きないようにしました。

loading="lazy"はまだ Safari などのモバイルブラウザで正式にサポートされていませんが、今後サポートが進めば簡単に画像の遅延読み込みを可能にできるので期待しています。

JavaScript の最適化

ファーストビューに関係のない DOM のレンダリング処理が多く行われていたので、レンダリングを遅延させるようにしました。また、お知らせのレコード一覧では非同期なデータ取得とそのレンダリングによってレイアウトシフトが起こっていたので、表示エリアの高さをあらかじめ確保するようにして、レイアウトシフトが起こらないようにしました。

そのほか、複数の非同期処理の並列化や JavaScript の遅延ロードができないかと試みましたが、時間内では成果を得られませんでした。

改善の結果

これらの対応を行った結果、Lighthouse でのスコアが 20 ほど向上しました。

f:id:cybozuinsideout:20210225110650p:plain
対応前(左)と対応後(右)のスコア

これらの対応はまだプロダクトへと反映できていませんが、徐々に改善を進めていければと思います。

Chrome の Local Overrides が便利

今回、ローカル環境で実際に使われているのと同じようなデータを事前に用意できなかったこともあり、計測と改善を Chrome のLocal Overridesを使って行いました。

プロダクトの本番環境に適用せずに手元で変更したコードが反映できるので、短い時間で変更と計測を繰り返せました。オススメ機能ですので、まだ試したことがないのであればぜひ一度試してみてください。

おわりに

フロントエンドエキスパートチーム内で開催したスピードハッカソンついての紹介でした。

今回、普段あまり一緒にモブなどをする機会の少ないメンバー同士がチームとしてワイワイ作業ができました。どのチームもプロダクトを題材に選んだことで、プロダクトのコードへの理解や、実際に改善に取り組む際の障壁や課題の理解の助けになりました。

お試しで実施してみたハッカソンでしたが、チームにとってもプロダクトにとっても得られるものが多くあったので、今後もテーマを変えて継続的に開催していきたいと思います。

Goの静的解析ツールをgolintからstaticcheckに移行した話

$
0
0

こんにちは、@ueokandeです。 つい先日Go 1.16がリリースされましたね。 サイボウズではGoで書かれたプロダクトやツールなどが数多く存在しており、GitHubで公開しているものも多数あります。

自分の所属するYakumoチームでも、Kubernetes上にデプロイしているサービス、AWS Lambda、開発者向けのツールなどをGoで実装しています。 本日は、Goの静的解析ツールをgolintからstaticcheckに移行したお話をします。

x/lint: freeze and deprecate

Goコードの静的解析ツールとして、Yakumoチームではgolintを採用していました。 golintはGoの静的解析ツールとして、長らくデファクトスタンダードとされており、サイボウズ社内でも多くのチームで利用していました。 しかしgolintの開発チームから今後はメンテナンスされないことが発表され、利用も非推奨となりました。 Yakumoチームも含めた社内でgolintを使っているチームは移行を迫られました。

github.com

Go静的解析ツールの選定

golintの移行先として、いくつかのGo静的解析ツールが挙がりました。

その中でYakumoチームではstaticcheckを採用しました。 採用理由は、staticcheckはデフォルト設定でも程よいルールが適用され、golintほど過剰な指摘がないという点です。 またstaticcheckはGoogleが公式でスポンサーをしており、社内のNecoチームがすでに利用していたというのも理由の1つです。

ここで挙げたツールとgolintは、指摘内容が異なる点に注意する必要があります。 これらのツールはバグとなりうるコードを指摘するのに対して、golintはスタイルチェッカーです*1。 例えばgolintはexportされた関数へのドキュメントコメントを強制しますが、staticcheckではデフォルトでそのチェックが無効になっています。 またstaticcheckは、err確認漏れ (if err != nil)を指摘してくれますが、golintにはその機能がありません。

golintからstaticcheckへの移行

CI/CDへの導入

まずやることは、CI/CDパイプラインで利用しているgolintをstaticcheckに置き換えることです。 試しに既存のコードをstaticcheckでチェックしてみたところ、非常に多くの箇所がエラーになることがわかりました。 コードの差分が多くなると、レビュワーへの負担になったり、レビュー漏れが発生しそうです。 またレビューをしている間にコンフリクトが発生して、レビューが一向に終わらないという懸念点もありました。

そこで最初のステップとして、staticcheckへの置き換えのみを目的にしたPull Req.を作成します。 既存のエラーはstaticcheckでスキップするようにコマンドラインで指定して、CI/CDパイプラインは通過するようにします。 レビュワーにはstaticcheckの導入にのみ集中してもらいたいため、既存のGoコードは一切手を加えません。

- test -z "$(go list ./... | xargs golint | grep -v ': exported ' | tee /dev/stderr)"+ staticcheck -checks inherit,-S1038,-S1007,-ST1005,-U1000,-S1039,-S1002,-SA1006,-SA4006,-S1021,-S1030,-S1004,-S1034,-SA6005,-S1016,-S1036,-S1028,-SA4011,-ST1011 ./...

もちろんこの時点では潜在的な不具合やエラーになりやすいコードは残ったままですが、レビュワーへの負担は減りPull Req.もすぐにマージできました。 いち早くレポジトリにstaticcheckに移行することで、新たなエラーを防ぐこともできます。

ルールの有効化

さてstaticcheckが導入できれば、あとは地道にエラーを1つずつ外していくのみです。 この作業も、変更点が大きくなりすぎないように、適度な粒度でエラーを外していきます。 これをすべてのエラーがなくなるまで、いくつかのPull Req.に分けてルールの有効化を進めました。

staticcheckのルールを段階的に有効化していく
staticcheckのルールを段階的に有効化していく

最終的にはスキップするルールがゼロにして、無事移行完了です。

サイボウズはstaticcheckを応援しています

サイボウズ社内ではstaticcheckを広く利用しているため、サイボウズからstaticcheckの作者へ寄付することにしました。

github.com

寄付は「投資」ではなく、そのOSSを利用したおかげで事業が成長できた分を還元していく「フィードバック」であるという考えのもとに、 サイボウズはこれからもOSSに寄付をしていきます。

ITエンジニアを育成する場づくり〜教諭経験も生かしたU-16プログラミングコンテストの支援〜

$
0
0

開発本部 コネクト支援チーム所属の西原(@tomio2480)です.本記事では西原が関わりのある U-16 プログラミングコンテストにまつわる活動の紹介とお誘いについて触れます.
 
U-16 プログラミングコンテスト*1は 2010 年より開かれている北海道旭川市発祥のプログラミングコンテストです.西原ははじめのころより関わりがあります.2020 年度はコロナ禍の中でも 8 道県,12 大会が開かれました.2021 年 1 月に 2020 年度に開かれた各大会で優秀な成績をおさめた児童,生徒を対象とする表彰式が開催されシーズンが終わりました.前回表彰式は以下ツイートのような形で東京に各選手を招待しての式でしたが,今回はオンラインでした.

 
コネクト支援チームでは旭川兼北海道大会に協賛,また旭川兼北海道大会,札幌大会函館大会に西原が運営として参加*2し,お手伝いを行いました.2020 年がはじめての協賛でしたので,これからは全体にも関わっていくことができればと考えています.
 
ここでは 2 つの部門(競技部門,作品部門)について,それから,この大会がいかにして IT エンジニアの育成に資する場であろうとしているかをお話しします.この記事を通じて,一人でも多くの方に興味を持っていただき,あわよくばその地方で大会を開きたいという声が上がり,自分も運営のお手伝いができると大変喜ばしいです.  

競技部門と作品部門

コンテストは 2 部門構成です.地域によってはどちらかの部門だけ開催という形もあります.  

競技部門

CHaser (チェイサー) という競技プラットフォームを利用した対戦を行います.これは全国情報技術教育研究会が行っている高校生向けのプログラミングコンテストで使用されていたもので 2010 年大会の後に使用許可をとり*3,U-16 プログラミングコンテストで使用しています.大会参加人数によって総当りであったりトーナメントであったり形式はそれぞれです.以下の画像は第10回U-16旭川プログラミングコンテスト・第7回U-16プログラミングコンテスト北海道大会の決勝トーナメントの様子です.

YouTube のスクリーンショットを用いた競技の様子の説明画像
競技プラットフォーム CHaser 上での競技の様子
 
C : Cool(先攻) と H : Hot(後攻) がそれぞれの選手のプログラムにより動作し,アイテム(ダイヤモンド)を集めます.C と H のクライアントは Walk,Look,Search,Put の 4 コマンドに上下左右の方向をもたせた 16 種類の命令を駆使してマップ上を練り歩きます.勝敗については,自滅しないことや相手をブロックでつぶした場合など,攻撃等も考慮されて決定します.これらを踏まえて練られた競技ルールは全国統一ではありません.各地域で旭川大会のルールに手を加えたルールを使用しています.一つの例としてこちらの動画で旭川大会のルールがご覧いただけます.
 
もう少し説明すると,ゲームサーバーとクライアントの間でソケット通信が行われ(pp.14-15)ゲームが進行します.選手がコードを書くのはクライアント側です.しかし,小中学生にゼロからソケット通信のプログラムを書きなさいというのは優しくないので,通信部分はライブラリの提供もあります
 
もともとは Java,その後 C が出てきて,旭川高専の学生の手によって HSP,釧路高専の学生の手によって Ruby,旭川工業高校の先生の手により C#...... と,どんどん増え,各地域で扱いたい言語のものを各地域の大会をやりたい人が作っています.今は西原が書いた Python のものを使っている地域もそこそこ,長野では Scratch が登場しさらなる低年齢化が進んでいるようです.
 
コロナ禍の前は以下のような感じで会場に集まってはみんなで大きい画面を見てわいのわいのしておりましたが,2020 年度は YouTube のコメント欄や Zoom チャットでわいわいという感じでした.  

作品部門

一方こちらは自由製作の部門になります.特に旭川は,パソコンを使いこなしている児童生徒を褒めたい一心で「パソコンで作っていれば何でもよい」という,もはやプログラミングってなんですか状態のルールです*4.もちろん地域によってこのあたりも違いが出ます.写真は 2019 年に豊田高専で開かれた愛知大会の様子です.

スクリーンに映されているロボットの様子
愛知大会での作品紹介,無線コントローラーで操作できるロボット

パソコンに映るシューティングゲームの開始画面
愛知大会での作品紹介,シューティングゲーム

いかにして IT エンジニアの育成に資する場であるか

U-16 プログラミングコンテストの特徴として,地域のできる範囲で大会を開こうという方針があります.そのため,ルールが地域ごとにアレンジされていたり,使用言語が異なっていたり,講習会があったりなかったり...... そこもできる範囲でそれぞれです.
 
その地域地域で教えられる人の数も分野も異なりますし,生徒児童のレベル感も異なります.ある一定のレベルに引き上げることを考えると,全国統一ルールの大会もあってほしいのですが,それだけでは各地域それぞれで育てるには難しいこともあります.実際,工業高校が取り組むいくつかの大会では,全国大会のルールを元に地方大会のルールを改め,易化したもので進行しています*5
 
実は中途半端に U-16 として高校 1 年生を含めたのには理由がありました.高校 1 年生まで挑戦できて,学年が上がったら教える側に入っていってほしいという願いの込められた年齢設定です.  

高校生が中学生にプログラミングを教える様子
ふらのみらいらぼでの U-16 プログラミングコンテスト講習会は高校生が教えています

Zoom で西原が中学生からの質問に答える様子
高校生が忙しいときは大人で対応します
 
これはパソコン部や文化部と呼ばれる部活の悲しい現状でありますが,取り組むべきことを顧問でも見つけられず,ひたすら Word, Excel, PowerPoint を触るとか,学校の貧弱な環境でできるそれらしい開発として HTML/CSS ,Scratch を頑張るとか,そういうことに取り組みながらも,高体連や高文連のような大きな目標を持てずにいる部活もたくさんあるのです.最近ではプログラミングコンテスト等も増えてきてはいますが,新しい分野で指導側も苦しくなかなかそこに踏み出せない部活も多いと感じています*6

そのため,部活で取り組んでもらえる目標として U-16 プログラミングコンテストを開き,選手としてだけではなく講師としても関わることができる活動目標としてありつづけることが考えられました.選手として戦ってきた後にコーチとして後輩を指導するという人材育成のループを作り出さんとする取り組みとして大会を開くのです.
 
この仕組みが出来上がって満足するだけでなく,別の地域にも広めていき,よりたくさんの IT エンジニアを目指す卵たちが学びうる環境を作ることも大事な活動です.また,継続可能な取り組みとする一環として高校生や高専生が講師として中学校に訪問する際は,お小遣い程度ではあるものの,実行委員会から日当もでます.若いうちからタダ働きに体が慣れるのはよくないので,これは重要な要素です.  

函館高専での U-16 プログラミングコンテスト講習会の様子
函館で新たに開きたいという要望があったので富良野から講習会を開きにいきました
 
現在 8 道県でこの U-16 プログラミングコンテストの仕組みを使って,たくさんの学びの環境がつくりあげられています.以下は BCN AWARD 2021 のパンフレットからの引用です.また,ここには挙げられていませんが,実は北海道富良野市でも U-16 プログラミングコンテスト参加に向けた活動が 4 年間も続いています.西原は主にこの富良野市での活動を支援してきました.2020 年度の富良野勢の小さな表彰式は記事にもなっています.
日本地図上にプロットされた大会開催地区の情報
2020 年度の大会開催地域
 

サイボウズの人として関わる

旭川大会では広告として A4 一枚のメッセージを出しました.製作は西原と同じコネクト支援チームの上岡(@ueokande)さんにお願いしました.

サイボウズの 3 人の社員から IT エンジニアの卵たちに向けたメッセージ
旭川大会に出したサイボウズのメッセージ
 
プログラミングを学んだ先に IT エンジニアとしてのキャリアがある,という一例として児童生徒に我々の存在を認知してもらうことも大事なことです.プログラミングを始めたばかりのころは,技術自体ばかりに注目して学習を進めていくものだと思いますが,いずれ来る職業選択の際に IT エンジニアが候補に入るかどうかはそれだけでは決まらないかなと思っています.
 
「プログラミングをする」というだけの理解ではなく,何をするためにプログラミングを使っているか,ということを頭の片隅にでも入れておいてもらえると,より確からしい進路選択につながるだろうという思いが込められています.
 
また,児童生徒たちが今暮らしているところ出身の IT エンジニアがどんな仕事をしているか知ることができれば,より身近な職業だと感じてもらえるのではないかという思いも込め,北海道にゆかりのある社員のかんたんな自己紹介を掲載しました.協力してもらったのはフロントエンドエキスパートチームの笠間(@b4h0-c4t)さん,モバイルチームの今野(@rkonno)さん,Slash/CyDE-C チームの竹内(@lrf141)さんの 3 名です.普段から近くにこういった職種の人がいるのがベストではありますが,同郷の先輩がそうした仕事をしているということも距離が縮まる一つの要因かなという妄想の元に作られたものです.
 
これらのメッセージは学校教諭時代の自分では発信し得なかったかなと思っています.サイボウズの人となった今だからこそできることです.  

ともに技術と向き合う

こうして IT エンジニアへの一歩を踏み出した若者が,気楽に我々とともに技術で遊べる未来が楽しみで仕方ありません.もしかすると一緒に働くことになるかもしれません.今日明日ではない遠い未来ではありながら,日本全体の IT エンジニアの人数増加やレベル向上に資する活動であると信じて活動しています. ぜひ西原がやっているような勉強会にも参加してもらって,さらに交流機会を持ち,切磋琢磨したいものです.(彼ら自身が勉強会を開く未来も楽しい)
 
引き続きこうした未来への投資活動を続けていく所存です.こういった活動に興味のある方は社内外問わずぜひ西原(@tomio2480)までお声かけください.どうぞよろしくお願いします!

*1:IT ジュニア育成交流協会の Web ページをみると「U-15/U-16」と表記されていることにお気づきになられたかと思います.もともと旭川で生まれたときは U-16 つまり,高校 1 年生までを参加対象として大会を開いていました.しかし,各地域に展開する中で地域よって事情が異なり,U-15 としたいところはそのようにレギュレーションを決めたため,このような表記になっています.

*2:実は 2010 年の大会開催前から関わっているので,サイボウズに来てからも関わっているだけです.

*3:2010 年に北海道旭川工業高校の情報処理部が大会参加した際,西原も OB として埼玉までつれていってもらいました.その際に,情報処理部顧問であり U-16 プログラミングコンテスト首謀者の一人である下村先生がこれはいけると心動かされた瞬間を見ていました.

*4:そのため,小説やイラストも作品部門へ応募してよいことになっています.Arduino や Unity のゲームとイラスト,小説がならぶ大変多様な空間が広がります.

*5:例えば全国高等学校ロボット競技大会が挙げられます.ちょっとソースが提示できないのですが,西原が高校時代,また高校教諭時代に顧問として委員を仰せつかった際にはそういった運用でした.

*6:高校教諭経験がある自分だと,専門でない先生が顧問をとりあえずやるというパターンが多いという現場も経験済みで,そこの難しさは手に取るようにわかっているつもりです.

#opendevcon でサイボウズと技術同人誌と部屋とkintoneとセンサーのおはなしをしました

$
0
0

開発本部 コネクト支援チームの西原(@tomio2480)です.
 
Open Developers Conference 2020 Online にて「サイボウズと技術同人誌と部屋とkintoneとセンサーと私」という題のセッションを行いました.登壇者はシステムコンサルティング本部 Developer Leading 部の恵良さんと西原の 2 名です.本記事では簡単にセッションの内容を振り返ることとします.当日の Twitter はこちら

 
当日の動画とスライドは以下からご覧いただけます.

www.youtube.com

www.youtube.com

speakerdeck.com

サイボウズと技術同人誌の関わり

はじめに恵良さんから説明をいただきました.Developer Leading 部では開発者向けにkintone や Garoon など製品の技術関連情報を提供したり,認知活動を行っているとのことです.しばしばコネクト支援チームとも活動領域がかぶることもある部署になります.様々な媒体を活用して認知活動を広げる中で,王道である Web サイトや勉強会の展開では広がりに限界を感じることがあったというお話を伺いました.
 
西原も技術同人誌を書くにあたり,展開されている Web サイトである cybozu developer networkを見て回ったのですが,以下の例のような kintone に直接関係のない技術資料も用意されていて,ここまでコンテンツを作り込んだにも関わらず広まっていかないというのは確かに悲しいと思いました.

developer.cybozu.io

では書籍というなら,技術同人誌でなくとも商業出版すればよいのでは?という疑問も浮かんで来ます.しかしそう甘くなく,知名度の問題やきちんと売れていかなければならないといった,単に広めたいという思いだけではクリアできないハードルがあるのもまた事実.そんな中で技術同人誌との出会いを果たしたとおっしゃっていました.  

技術同人誌の文化を壊すことなく企業が馴染むには

恵良さんは技術同人誌を学ぶために,コミックマーケットや技術書展,MakerFaire などたくさんのイベントに参加され,現場の空気を肌で感じてこられ,その中で趣味でやられている皆さんが持たれている,好きなこと,面白いと感じていることを伝えたいという思いの理解につながったようです.
 
気持ちとしては変わらないが,企業として入っていくとおかしなことにならないか,心配は尽きなかったようです.考えた末に,いち参加者としてイベントを盛り上げながら,真摯に技術に向きあうことで,技術同人誌文化を壊すことなく発信につなげていくことができたということでした.
 
同人誌を販売する段階になって気づいたよいこともあったとのことです.購入者と直接お話ができたり,同じく技術同人誌を書いている人,開発をしている人と交流する機会がセットでやってくることは商業出版ではなかなか叶えられないことというのは納得感があります.  

恵良さんがお話されている様子の YouTube スクリーンショット
技術同人誌文化を大事にしながら入っていくための試行錯誤をお話する恵良さん
 
これはコネクト支援チームの活動にも似ている部分があったので,強い共感が生まれました.IT コミュニティの文化圏に入っていって,一緒に IT エンジニア文化を大事に育てていくことを意識して活動している我々と配慮していることも似ていました.
 
自分らの IT コミュニティとの関わり方も同じで,中に入ってみて一緒になってやってみないと,全体が考えていることや大事にしていることは中々わかりません.勉強だけでもいけません.そして,与えることの大事さも忘れてはなりません.
 
こちらに有利なことばかりを推し進めて,自分が欲しいものを手に入れてばかりでは,みんなが支えてきた文化を食いものにして,すばらしい文化を一つ壊すことになります.このバランス感を持たずして技術同人誌や IT コミュニティの文化圏に入っていくことは危ないことかなと,個人的には思います*1

技術同人誌を書いて得た学び

セッション中盤は恵良さんから西原にバトンタッチして,はじめて技術同人誌を書いた人目線でのお話をさせていただきました.実際に世に出ていった技術同人誌は以下のリンクから辿ることができます.
 
page.cybozu.co.jp

はじめての経験でたくさんの学びがあったのですが,最も感じたことは人に見せる前提でモノを書くと,今以上に勉強しなければ不安が軽減されないということです.技術的に間違っていないだろうか,この言い回しで正しく組み上げられるだろうか,画像を挟んだほうがいいだろうか,挟まないほうがスッと頭に入るような気もする...... といった学ぶ視点に立った障壁を減らすための思索が絶えません.
 
伝えたい,書きたいこと以上に知っていないと,何を伝えておくべきか,どこまでのレベルを書いておくべきかの適切(そうな)落とし所を自分で決めることができず,なかなか筆の進まない状況が続いてしまいます.西原は複業で専門学校の授業を受け持っていますが,授業資料とは違って読者とともに見る前提の資料ではないため,口頭で相手の顔を見ながら調整できないという点において,似ているようで似ていない作業だったなと感じています.
 
一方,これを利用すると,自分のレベルいっぱいのことを書くことに決めてしまえば,そこからレベルアップしないと書ききれませんから,間違いなく成長可能と見ています.学びに停滞感を感じている人は本を書くことで,半ば強制的に伸ばすというのも学習方法のひとつとして,悪い方法ではないと思います.
 
もちろん,これが単に発表や動画収録でもいいのですが,本というある程度体系化された知識を形にする必要のある方法を取ることによる効果は,他の方法にはない効果かなとも感じています.

みなさんも kintone 関係の同人誌を書いてみませんか?

談笑する恵良さんと西原の YouTube スクリーンショット
技術同人誌も今回のイベントも協力の賜物でした

今回,西原はもともと持っていたネタを kintone と組み合わせることで,サイボウズの中で技術同人誌を書くことができました.本来ならば一人でやらなければならない作業も分担してできて,最初の一歩としては実は挫折しにくい体制で勉強させてもらえたのかなと感じています.
 
具体的には最終レイアウト決め,頒布やイベント参加の申込に関する事務処理をお願いしたので,本当にコンテンツとせいぜい表紙くらいしかやっていません.結構,この事務手続きの部分で心が折れてしまう人もいるのではないかと思うので,こういった分担をすることで,よりたくさんのコンテンツが世に出てくるようになるかもしれません.
 
Developer Leading 部では引き続き kintone の技術同人誌を一緒に書いてくれる人を募集しているとのことです.もし,kintone に興味があって何か試してみた,作ってみた,そしてそれを伝えたいという方がいらっしゃいましたら,ぜひこちらのリンクより「同人誌執筆希望」にチェックをいれてご連絡ください!

*1:地方で IT コミュニティを運営してきて 10 年近く経つので,ここは完全に感覚でモノを言っています.

VictoriaMetrics と Grafana による Kubernetes クラスタのモニタリング

$
0
0

こんにちは。 Necoチームの梅澤です。

従前、Neco ではクラスタのモニタリングに Prometheusを利用していましたが、最近これを VictoriaMetrics + VictoriaMetrics operatorに変更しました。 本記事では、Prometheus で感じていた問題点と、それをどのように解決したかを紹介します。

感じていた問題点

我々が(オペレーターを利用しない) Prometheus で問題を感じていたのは以下の点になります。

  • メトリクスの長期保存
    • Neco では元々14日間ぶんを保存していました。しかし、過去のメトリクスは障害の継続的な調査にも有用であり、14日間では物足りなく感じていました。一方、 Prometheus はストレージの構造として長期保存をあまり想定していません。 https://prometheus.io/docs/prometheus/latest/storage/#operational-aspects
  • HA 構成
    • 以前の記事にあるとおり、Neco では比較的頻繁に全体再起動を行っているのですが、その間にモニタリングが止まってしまうのは避けなければなりません。 Prometheus を単に複数並べた冗長化は可能ですが、その場合ストレージはそれぞれが別個に持つことになります。自インスタンスのデータが欠けても他インスタンスのデータは参照できないので、これでは具合が悪いです。
  • マルチテナンシー
    • Neco 環境では Kubernetes 環境を開発/運用しているチーム(Neco チーム)と、その上で動くアプリケーションを開発/運用しているチーム(テナント。複数ある)は別であり、それぞれがそれぞれのコンポーネントをモニタリングしています。モニタリングの系はそれぞれで立てる必要がありますが、できれば省力化したいところです。

代替案

メトリクスの長期保存について考えてみると、 VictoriaMetrics の他に Thanos, Cortex, M3DBといった選択肢が挙げられます。 VictoriaMetrics は以下のような特徴があります。

  • 構成がシンプル
  • タイムシリーズストレージとして効率的(圧縮比など)
  • 機能ごとにうまく分割されており、 Prometheus から段階的に移行することも可能
  • drop-in replacement を称しており、仮に全体を置き換えても、使用感が変わらない

VictoriaMetrics は元々 Prometheus 向けのリモートストレージとして開発されましたが、その後 Prometheus と互換性のあるスクレイピングアラート判定のためのコンポーネントが追加され、Prometheus の drop-in replacement として十分な機能があります*1。 また、最近はオペレーター実装も開発が進んでいます。そのため、オペレーターを使ってモニタリングを全体的に VictoriaMetrics に移行することとしました。

VictoriaMetrics にするとどうなるか

メトリクスの長期保存

VictoriaMetrics は長期保存を想定して実装されています。 Neco 環境ではひとまず保存期間を1年強に設定して運用を開始しています。VictoriaMetrics は Prometheus と比較してデータの圧縮能力が高いため、長めの保存期間でもストレージの消費が少ないのがうれしいところです。

HA 構成

VictoriaMetrics には cluster versionというものがあります。これは、データのレプリケーションと読み書きリクエストの分散によって、ストレージに関してスケールアウト可能なHA構成を実現するものです。また、スクレイピングやアラート判定についても単純に複数レプリカを立てることで HA 構成を取ることができます。

マルチテナンシー

オペレーターを導入したため、テナントはカスタムリソースをちょっと記述するだけでモニタリングクラスタを立てることができます。例えば、必要なコンポーネントは以下のカスタムリソースでデプロイできます。(Alertmanager 向けの設定を保存する Secret は省略)

apiVersion: operator.victoriametrics.com/v1beta1
kind: VMAgent
metadata:name: vmagent
  namespace: tenant-ns
spec:replicaCount:3remoteWrite:- url:"http://vminsert-vmcluster-largeset.monitoring.svc:8480/insert/0/prometheus/api/v1/write"---apiVersion: operator.victoriametrics.com/v1beta1
kind: VMAlertmanager
metadata:name: vmam
  namespace: tenant-ns
spec:replicaCount:3configSecret: vmam-config
---apiVersion: operator.victoriametrics.com/v1beta1
kind: VMAlert
metadata:name: vmalert
  namespace: tenant-ns
spec:replicaCount:3datasource:url:"http://vmselect-vmcluster-largeset.monitoring.svc:8481/select/0/prometheus"notifiers:- url:"http://vmalertmanager-vmam-0.vmalertmanager-vmam.tenant-ns.svc:9093"- url:"http://vmalertmanager-vmam-1.vmalertmanager-vmam.tenant-ns.svc:9093"- url:"http://vmalertmanager-vmam-2.vmalertmanager-vmam.tenant-ns.svc:9093"

cluster version のストレージにはマルチテナンシー機能があって、テナントごとに自テナントだけが読み書きできるストレージのように振舞うことができます。しかし、Neco ではこの機能を使わずに、1つのストレージとして使うことにしました。この場合、テナントは自分でスクレイプしたメトリクスとNeco チーム側でスクレイプした cAdvisor や kube-state-metrics のメトリクスを組み合わせて独自のアラートルールを記述することができます。

さらなる可用性

実際にデータが保存されるストレージは Ceph RBD を使うことにしました。これにより、ストレージデバイスの故障に対しても耐性を持たせています。一方、そうすると今度は Ceph の障害が発生するとモニタリングが巻き込まれて一緒に止まってしまう懸念があります。これではいけません。

そこで、Ceph と本来のモニタリングクラスタをモニタリングするための最小限のモニタリングクラスタを、Ceph を使わずに用意することにしました。Neco では、この最小限のクラスタを smallset 、全体を監視する方のクラスタを largeset と呼んでいます。 smallset はあくまでも補助として使うもので、ストレージに TopoLVM を使い、保存期間も短めです。2つのクラスタを含むモニタリングの構成図を以下に示します。

Neco 環境における VictoriaMetrics の構成図
Neco 環境における VictoriaMetrics の構成図

Grafana

メトリクスの可視化には Grafana を使うのが一般的です。VictoriaMetrics は上記のようにマルチテナンシーを実現しましたが、Grafana も同様にマルチテナンシーを実現する必要があります。 Neco では以前から grafana-operatorを使用しており、ダッシュボード定義をカスタムリソースとして記述すると、Neco 環境内で稼働している Grafana にダッシュボードを追加できるようにしてあります。これにより、テナント側でダッシュボードを自由に追加することができます。

コントリビューション

VictoriaMetrics operator は開発が始まったばかりであり、いざ使ってみようとすると、やはりいろいろと問題や機能不足が見つかるものです。Neco チームでは継続的に upstream にコントリビューションをしています。現在、 feature request を投げると結構早く実装してもらえるのもうれしいところです。

最後に

本記事では VictoriaMetrics + operator を使用したモニタリングの構成例を紹介しました。高可用化を実現できたことで、ラック丸ごと障害の訓練時でも元気にアラートを発出できています。皆さんのより良いモニタリングライフの参考になれば幸いです。

*1:Alertmanager に相当するものは VictoriaMetrics の方には実装がないので、Prometheus の実装をそのまま使います。

GitHub Sponsorsを使って「企業」として寄付をした話

$
0
0

こんにちは。OSS推進チームの平野(@shisama_)です。

2020年12月にGitHub Sponsorsを利用して企業からOSSや個人へ寄付できるようになりました。

www.publickey1.jp

この記事では企業からOSSや開発者へGitHub Sponsorsにて寄付する方法について紹介します。

社外のOSSや開発者を支援するモチベーション

サイボウズでは日頃の業務でお世話になっているOSSへの寄付をしています。

tech.cybozu.io

サイボウズのプロダクト開発には多くのOSSが利用されており、プロダクトの成長はOSSによって支えられています。そこで、そのOSSのおかげで事業が成長した分を還元していく「フィードバック」として寄付を行い、OSSが持続的に発展できるよう支援しています。

これまでOSSプロジェクトに対してOpen Collectiveなどのプラットフォームの利用や直接送金により支援してきました。前述のとおり、GitHub Sponsorsが企業に対応したことにより個人開発者への寄付もしやすくなりました。

とくに開発業務でお世話になっている個人開発者の活動を支援するために、OSS推進チーム主体でGitHub Sponsorsを使った寄付をはじめました。

GitHub Sponsorsを使った企業からの寄付方法

Organizationアカウント(企業)からの寄付について、日本ではまだ事例が少ないと感じています。特に請求書払いのOrganizationアカウントからの寄付方法についての方法は調べても見つかりませんでした。 ここでは請求書払いのOrganizationアカウントを持つ企業からの寄付方法について説明します。

現在、OrganizationアカウントについてはクレジットカードまたはPayPalでの支払いのみGitHub Sponsorsで寄付ができます。

GitHub Sponsors for companies: Your company can now invest in your most critical open source dependencies through PayPal or a credit card (with more payment options coming soon).

引用元: New from Universe 2020: Dark mode, GitHub Sponsors for companies, and more - The GitHub Blog(参照日時2021/03/05)

サイボウズではGitHub Enterprise Cloudを利用しており、各OrganizationにおけるGitHubの有償機能の支払いをEnterpriseアカウントで集約し、請求書払いで処理しています。
前述のようにGitHub SponsorsはクレジットカードかPayPalでの支払いにしか対応していません。そのため、Enterpriseアカウント配下の@cybozu organizationで寄付しようとすると次のようなメッセージが表示され寄付できませんでした。

Sponsorships from invoiced organizations is in beta.

請求書払いできないメッセージ表示

「Join waitlist」ボタンをクリックし、しばらく待ってみましたが進展がなさそうだったので、GitHubに問い合わせました。すると、次のような手順に従って支払うことでメインアカウントから寄付したことにできると回答をいただきました。

  1. 現在メインで使っている請求書払いのアカウント(@cybozu)とは別に支払い用のサブアカウントを作成
  2. 作成したサブアカウントからクレジットカードまたはPayPalで支払い
  3. メインアカウントにリンクさせる

サブアカウントでGitHubに支払う概要図

Organizationアカウントから寄付をしたい場合の条件を整理すると、次のようになります。

  • 寄付を実施したいOrganizationアカウントの支払い方法がクレジットカード、またはPayPalに設定されている
    • そのまま何もせずに、そのアカウントから寄付できます
  • 寄付を実施したいOrganizationアカウントに上記以外の支払い方法(請求書払い)が設定されている
    • 新たにクレジットカードまたはPayPal払いを設定したOrganizationアカウントを作成し、元のアカウントと紐付ける必要があります
    • Enterpriseアカウントで請求を集約して請求書払いにしている場合もこちらに該当します

ここから先は、後者のケースについて具体的な手順をご紹介していきます。

請求書払いの企業からのSponsoring

次の手順によって請求書払いの企業でもGitHub Sponsorsから寄付ができるようになります。

今回はサイボウズが実際に@azu氏に寄付をした際の画面とともに説明します。

1. メインアカウントとは別の支払い用のサブアカウントを作成する

はじめに、これまでメインで使っていた請求書払いのOrganizationアカウントとは別に支払い用のOrganizationアカウントを作成します。

サイボウズでは@cybozu-sponsorshipという支払い用のOrganizationアカウントを作成しました。

Organizationアカウントの作成については以下のリンク先をご参照ください。

2. サブアカウントから対象のアカウントに寄付

寄付したいGitHubアカウントのページの左側にあるSponsorボタンを押下し、GitHub Sponsorsページに遷移します。

GitHub Sponsorsページへのボタン

GitHub Sponsorsページに遷移すると、次のように右側でどのアカウントから寄付するか選択できるので、作成したサブアカウントを選択します。

GitHub Sponsorsページで支払いアカウントを選択する画面

サブアカウントを選択したあとは寄付プランを選択して、クレジットカードまたはPayPalを使い支払いを完了させます。

GitHub Sponsorsにて寄付をする詳細な方法については以下のリンク先をご参照ください。

3. サブアカウントとメインアカウントをリンクさせる

この時点でサブアカウント(@cybozu-sponsorship)で支払いはできましたが、本来はメインアカウント(@cybozu)から寄付したいと考えていました。

前述のとおり、メインアカウントは請求書払いのため直接寄付はできません。しかし、支払い用のサブアカウントとリンクさせることでメインアカウントもスポンサーとみなされます。

アカウントのリンクは、サブアカウント側のSponsoringページから行えます。 サブアカウントのSponsoringページを開き、Settingsボタンを押下します。

サブアカウントのSponsoringページのsettingsボタンを押下

次のようにスポンサーに関する設定画面に遷移します。寄付用のアカウントから他のアカウントにスポンサーシップをリンクさせることができます。サイボウズの場合だとリンク元が@cybozu-sponsorship、リンク先が@cybozuとなります。 リンクさせることで@cybozu-sponsorshipが支払った寄付の情報を@cybozuにも反映させることができます。

サブアカウントをメインアカウントにリンクする画面

リンクが成功していれば、メインアカウントのSponsoringページにも寄付したアカウントが表示されます。

Sponsoring · Cybozu · GitHub

請求書払いのOrganizationアカウントは直接寄付金を支払うことはできません。しかし、ここで紹介したように、支払いはサブアカウントから行い、アカウントをリンクすることでスポンサーとしてみなされます。

おわりに

OSSの持続可能性はプロダクトの成長に関係します。利用しているOSSのメンテナンスが止まってしまい、脆弱性対応やバグ修正がされなくなると他のOSSに移行するか自作する必要があります。規模によっては大工事になることもあります。

OSSのバグを見つけたときにコードの修正は難しく感じても、寄付は難しくありません。

より良いプロダクト開発をするために、本記事がOSS開発者の支援をはじめるきっかけになれば幸いです。


データセンター仮想化ツール Placemat v2の紹介

$
0
0

こんにちは、Necoチームの鈴木です。

Necoチームでは仮想データセンター構築ツールPlacematを使って、データセンターを丸ごと仮想化し、その上でサーバーのプロビジョニングやKubernetesクラスタ構築、Kubernetes上で動作するアプリケーションのTest Suitesを実行しています。 Placematはプロジェクト初期に開発されたツールで、古いツールに依存していたり、実装方式や設計が洗練されていないなどの課題があっため、4ヶ月前からv2を開発開始し、先日リリースしました。 本記事ではその機能と使い方、今後のCI改善 Placemat on Kubernetesについて紹介します。

特徴

  • シンプルな構成
  • YAMLの設定ファイルで多彩なデータセンター環境を再現可能
  • 多彩なVM設定
  • 仮想BMC

シンプルな構成

Placematはシングルバイナリで構成されています。 使い方もシンプルで、インストールしたバイナリにYAMLの設定ファイルを渡して起動すると、設定に従ってネットワークを構成してVMを起動、終了時にはそれらをクリーンアップします。 CIで自動テストを実行するための環境作りがとても楽になります。aptで依存ライブラリをインストール、debパッケージでPlacematをインストールして起動すれば完了です。

YAMLの設定ファイルで多彩なデータセンター環境を再現可能

下記のリソースをYAMLファイルに定義して組み合わせることで、様々な構成の仮想データセンターを構築することができます。

1: Networkリソースを定義することで、Bridgeネットワークを作成します。VMやスイッチを相互に接続できます。

kind: Network
name: my-net
type: external
use-nat:trueaddress: 10.0.0.0/22

2: NetworkNamespaceリソースを定義することで、独立したネットワーク領域を作成します。Network Namespace内でbirdなどのアプリケーションを実行することでスイッチの機能をエミュレーションすることができます。

kind: NetworkNamespace
name: my-netns
init-scripts:
  - /path/to/script
interfaces:
  - network: net0
    addresses:
      - 10.0.0.1/24
apps:
  - name: bird
    command:
    - /usr/local/bird/sbin/bird
    - -f
    - -c
    - /etc/bird/bird_core.conf

3: Nodeリソースを定義することでQEMUでVMを起動することができます。後述しますが多彩な設定が可能になっています。

kind: Node
name: my-node
interfaces:
  - net0
volumes:
  - kind: image
    name: root
    image: image-name
    copy-on-write: true
  - kind: localds
    name: seed
    user-data: user-data.yml
    network-config: network.yml
  - kind: raw
    name: data
    size: 10G
  - kind: hostPath
    name: host-data
    path: /var/lib/foo
    writable: false
ignition: my-node.ign
cpu: 2
memory: 4G
smbios:
  manufacturer: cybozu
  product: mk2
  serial: 1234abcd
uefi: false
tpm: true

4: Imageリソースでサーバーの起動ディスクイメージを定義できます。URLを指定してPlacematにダウンロードさせることもできますし、ローカルに保存済みのイメージファイルのパスを指定することもできます。

kind: Image
name: ubuntu-cloud-image
url: https://cloud-images.ubuntu.com/releases/16.04/release/ubuntu-16.04-server-cloudimg-amd64-disk1.img

Necoチームで使っている構成

Necoチームでは以下の構成の仮想データセンターを構築して自動テストに使っています。

仮想データセンターをテストに利用している図

  • Network Namespace、Bridgeをそれぞれ作成し、Vethを作って指定されたBridgeに繋ぐことでネットワークを構成する。そして、それぞれのNamespace内でbird、chrony、squid、dnsmasqをコンテナで起動し、SwitchのBGPリフレクタ、DHCP Relay、NTPサーバー、プロキシサーバーの機能をエミュレーションする。
  • Boot ServerやCompute SeverのVMを起動し、Tap Interfaceを使ってBridgeに繋いで、各RackのToR Switchに接続する。

多彩なVM設定

Nodeリソースは以下のような多彩なVMの設定が可能で、様々なユースケースに対応可能です。

  • プロビジョニング
    • cloud-init、ignitionを使ってサーバーのプロビジョニングを自動化することができ、テスト環境の構築を容易にします。
  • ファイル共有
    • hostPathvolumeを指定することで、virtio-9p-deviceを使ったホスト/ゲスト間のファイル共有が可能です。cloud-initやignitionでvolumeをmountすれば、テストに必要なファイルをホストから渡すこともできます。
  • TPM
    • Trusted Platform Module(TPM)をVMに設定することができます。TPMにディスク暗号化キーを保存するようなユースケースのテストを実行できます。
  • UEFI
    • 旧来のBIOSだけではなく、UEFIも利用することができます。

Quick Start

こちらのExampleを使ってPlacematを実際に動かす方法を紹介します。 動作環境はUbuntu 18.04/20.04になります。VMで動かす場合にはNested Virtualizationを有効にする必要があります。

ここでは以下のようなBootサーバー1台、Workerサーバー2台のClusterを構築していきます。

Exampleの構成

  • BootサーバーはUbuntu20.04のイメージを使って起動し、cloud-initで初期設定してdnsmasq, nginxを起動して、network bootサーバーとして機能する
  • WorkerサーバーはBootサーバーからiPXEをダウンロードし、Bootサーバーから提供されたFlatcar container linuxのイメージを使って起動する

使用する設定ファイルは以下になります。

kind: Network
name: net0
type: external
use-nat:trueaddress: 172.16.0.1/24
---kind: Network
name: bmc
type: bmc
use-nat:falseaddress: 172.16.1.1/24
---kind: Image
name: ubuntu-image
url: https://cloud-images.ubuntu.com/releases/20.04/release/ubuntu-20.04-server-cloudimg-amd64.img
---kind: Node
name: boot
interfaces:- net0
volumes:- kind: image
    name: root
    image: ubuntu-image
  - kind: localds
    name: seed
    user-data: user-data.example.yml
    network-config: network-config.example.yml
cpu:1memory: 2G
---kind: Node
name: worker-1
interfaces:- net0
volumes:- kind: raw
    name: data
    size: 10G
cpu:1memory: 2G
smbios:serial: 1234abcd
uefi:false---kind: Node
name: worker-2
interfaces:- net0
volumes:- kind: raw
    name: data
    size: 10G
cpu:1memory: 2G
smbios:serial: 5678efgh
uefi:false

インストール

以下のコマンドを実行して依存packageとPlacematをインストールします。

# 依存packageをインストール
$ sudo apt-get update
$ sudo apt-get -y install --no-install-recommends qemu qemu-kvm socat picocom cloud-utils freeipmi-tools

# Placematをインストール
$ curl -O -sfL https://github.com/cybozu-go/placemat/releases/download/v2.0.4/placemat2_2.0.4_amd64.deb
$ sudo dpkg -i ./placemat2_2.0.4_amd64.deb

Placematを起動

以下のコマンドを実行してplacematリポジトリをcloneし、exampleのclusterを構築します。

$ git clone https://github.com/cybozu-go/placemat.git
$ cd placemat/examples
$ sudo placemat2 --data-dir ./data --cache-dir ./cache ./cluster.example.yml

以下のログが表示されていれば起動成功です。

placemat2 info: "Start Placemat API server" address="127.0.0.1:10808"

サーバーにログイン

Placematを起動したコンソールとは別のコンソールを開き、以下のコマンドでnode(VM)の一覧を確認します。

$ pmctl2 node list
boot
worker-1
worker-2

続いてbootサーバーにログインします。

$ sudo pmctl2 node enter boot

# ユーザー: ubuntu, パスワード: ubuntu でログインできます。
# ターミナルを終了する時は、Ctrl-qとCtrl-xを続けて押してください。

worker-1にもログインしてみます。bootサーバーからOSイメージをダウンロードしてセットアップを終えるとログインできるようになります。環境によっては時間がかかる場合があります。

# worker-1にログイン
$ sudo pmctl2 node enter worker-1

# ターミナルを終了する時は、Ctrl-qとCtrl-xを続けて押してください。

BMCサーバーを起動

次にBMCサーバーを起動して、IPMI、Redfishを使ってVMを再起動してみます。ここではworker-1のBMCを起動して再起動してみたいと思います。

$ sudo pmctl2 node enter worker-1
$ echo 172.16.1.2 | sudo dd of=/dev/virtio-ports/placemat

# ターミナルを終了する時は、Ctrl-qとCtrl-xを続けて押してください。

PlacematはVMの/dev/virtio-ports/placematにcharデバイスを設定して、BMCアドレスが通知されるのを待機しています。 アドレスが通知されると以下のようなログを出力してBMCサーバーを起動します。

placemat2 info: "creating BMC port" bmc_address="172.16.1.2" serial="1234abcd"
placemat2 info: "BMC USer: Add user" user="cybozu"

ipmipowerコマンドを使ってworker-1の電源状態を取得してみます。

$ ipmipower --stat -u cybozu -p cybozu -h 172.16.1.2 -D LAN_2_0
172.16.1.2: on

続いてRedfishを使って電源状態を取得します。

$ curl -sku cybozu:cybozu https://172.16.1.2/redfish/v1/Systems/System.Embedded.1 | jq .PowerState
"On"

worker-1を再起動

ipmipowerコマンドでworker-1を再起動します。

$ ipmipower --reset -u cybozu -p cybozu -h 172.16.1.2 -D LAN_2_0
172.16.1.2: ok

worker-1のコンソールに入ると再起動してネットワークブートしている様子が確認できます。

$ sudo pmctl2 node enter worker-1
# ターミナルを終了する時は、Ctrl-qとCtrl-xを続けて押してください。

Redfishを使う場合はこちらのコマンドで再起動できます。

$ curl -X POST -H "Content-Type: application/json" -d '{"ResetType":"ForceRestart"}'  -sku cybozu:cybozu https://172.16.1.2/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset

Placematを終了する

Placematを起動したコンソールでCtrl-cを押します。作成したBridgeやNetwork Namespaceなどをクリーンアップして終了します。

今後のCI改善計画 - Placemat on Kubernetes

最近ではRook/CephやMySQL Operator MOCOなど重量級のアプリケーションが稼働を始めたことなどもあって、CIの不安定さや遅さが問題になることが増えてきました。

  • GCP上のNested VMが安定しない。VMが落ちてしまったり、起動してこないなどの不安定な挙動がある。
  • GCPのQuotaに引っかかってしまい、インスタンスを起動できないことがある。
  • GCPの利用料金がかさむ。
  • Nested VMのCPUやIOのボトルネックにより、CIの実行時間が長い。

これらの課題を解決するために、PlacematをKubernetes上で動かしてCIを回すことを計画しています。

github-actions-controllerのイメージ図

  • 自社データセンターのBare Metalサーバー上に構築したKubernetesクラスタ(Staging環境)のPod内で、仮想データセンターを構築する。
  • Github Actionsのself-hosted runnerを使って上記の環境でワークフローを実行する。

Self-hosted runnerをKubernetes Clusterで管理するためのOperatorを絶賛開発中です。 こちらのOperatorが完成したら、KubernetesのPod内に構築された仮想データセンターでワークフローを実行するように、CI環境の移行を進めていく予定です。 事前のPoCではCIの高速化が期待できる結果が出ているため、大幅なCI改善を見込んでいます。

KubernetesにおけるownerReferenceの誤用にご用心

$
0
0

はじめに

こんにちは、Necoプロジェクト兼ストレージチームのsatです。本記事はKubernetesのガベージコレクションにおけるownerReferenceというフィールドの役割、誤用したときの振る舞い、および問題を防止、検出する方法する方法について述べます。

本記事で扱う問題の実例としてRookを扱っていますが、とくにRookの知識は必要なく、Kubernetesについての基本的な知識があれば読める内容です。

KubernetesのガベージコレクションとownerReference

Kubernetesには使われていないリソースをバックグラウンドで自動的に削除するガベージコレクション機能があります。このとき、どのリソースが他のどのリソースに依存しているかを示すためにownerReferenceというフィールドを利用できます。ガベージコレクタは誰もownerがいないリソースは消してもいいと判断するというわけです。

ownerReferenceの使用にあたっては以下のことを守らなければなりません。

Note:
Cross-namespace owner references are disallowed by design.

Namespaced dependents can specify cluster-scoped or namespaced owners.
A namespaced owner must exist in the same namespace as the dependent.
If it does not, the owner reference is treated as absent, and the dependent is
subject to deletion once all owners are verified absent.

Cluster-scoped dependents can only specify cluster-scoped owners.

日本語で箇条書きすると、次のようなことが書いています。

  • namespaceをまたいだowneReferenceの設定は仕様上許されていない
  • namespacedリソースはcluster-scopedリソース、あるいはnamespacedリソースをownerにできる
  • cluster-scopedリソースはcluster-scopedリソースのみをownerにできる
  • ownerがnamespacedリソースの場合、子リソースは同じnamespaceになければいけない
  • 上記の仕様に違反したownerReferenceは無いものとみなされる。あるリソースのownerReferenceが不正なものだけの場合、当該リソースはガベージコレクションによる削除対象になる

本記事で扱うのは上記の仕様に違反した場合に発生する問題です。

ownerReferenceの仕様違反によって発生した問題

Rookにおいて昨年、上記仕様に違反したownerReferenceを設定したため、大きな問題が発生しました。

github.com

問題は、Rookが内部的に使うCephのCSIドライバ(ceph-csi)を管理するためのrook-ceph-csi-configというConfigMapが突然消えてしまうことがあるというものでした。

原因はRookではcluster resourceであるceph-csiのCSIDriverリソースのownerReferenceに、namespaced resourceであるRookのオペレータのDeploymentリソース(rook-ceph-operator)を指定していたことでした。これは上記仕様に反しています。

発生までの流れは次のようなものでした。

  1. KubernetesのガベージコレクションがCSIDriverリソースをチェックする(これは不定期に発生する)
  2. 上記オブジェクトのOwnerReferenceをチェックするが、rook-ceph-operatorという名前のDeployment "cluster" resourceは存在しない
  3. CSIDriverリソースはownerがいないので削除されてしまう
  4. CSIDriverリソースをownerとするrook-ceph-csi-configを含む他のリソースが、CSIDriver以外にownerがいなければ全て削除される

この問題は発生経緯がわかりにくいこと、およびRookではなくKubernetesの振る舞いによって引き起こされるもの(悪いのはRookですが)なこともあって、検出から修正まで一か月近くを要しました。

バグを修正するcommitは修正前、および修正後の両方を考慮して「既存のownerReferenceが不正なら修正する、そうでなければ何もしない」というものになりました。

github.com

github.com

このような変更はテストが面倒なことに加えてコードも汚くなりがちなので、できれば避けたいところです。

対策

本節では上記の問題の発生を防止あるいは検出する方法をそれぞれ紹介します。

問題の防止には、たとえばコントローラの実装にcontroller-runtimeを使っている場合はownerReferenceの設定時にSetOwnerReference、およびSetControllerReferenceという関数を使うことによってvalidationできます。

kubectl check-ownerreferencesというkubectlの拡張コマンドを使うと、動作中のKubernetesクラスタのリソースのvalidationをしてくれます。

Rookにおいては両方のvalidationを追加しました。

github.com

github.com

github.com

github.com

PRの変更を見ていただければわかるのですが、数千行にわたる大規模な修正が必要でした。このように修正が後になればなるほど変更量が増えるので、みなさんの環境でvalidationをしていない場合は、なるべく早く対策をすることをお勧めします。

Kubernetesにおいても「仕様違反をしたとしても、このような動作になるのはなるべく避けたいので、もう少しガベージコレクションの振る舞いを改善できないだろうか」という問題提起があり(上述のKubernetesのissueがそれです)、既に改善されています。

github.com

上述のownerReference仕様についても、以下のような但し書きが追加されています。

In v1.20+, if a cluster-scoped dependent specifies a namespaced kind as an owner,
it is treated as having an unresolveable owner reference, and is not able to be garbage collected.

In v1.20+, if the garbage collector detects an invalid cross-namespace ownerReference,
or a cluster-scoped dependent with an ownerReference referencing a namespaced kind,
a warning Event with a reason of OwnerRefInvalidNamespace and an involvedObject of the invalid
dependent is reported. You can check for that kind of Event by running
kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace.

おわりに

ownerReferenceにまつわる問題は一度発生してしまうと上述のようにトラブルシューティングに時間がかかりがちです。繰り返しになりますが、みなさんもvalidationを積極的に活用して問題の発生を未然に防止することをお勧めします。

最後になりますが、Necoプロジェクトおよびストレージチームでは一緒に働いてくれるかたを募集中です。ぜひ一度下記の募集要項をごらんください。

cybozu.co.jp

cybozu.co.jp

それぞれがどういうプロジェクト/チームであるかは以下リンク先のブログ/スライドをごらんください。

blog.cybozu.io

speakerdeck.com

「第10期サイボウズ・ラボユース成果発表会」開催

$
0
0

サイボウズ・ラボの光成です。 今回は2021年3月30日に開催された第10期サイボウズ・ラボユース成果発表会の模様を紹介します。

サイボウズ・ラボユース

サイボウズ・ラボユースとは日本の若手エンジニアを発掘し、育成する場を提供する制度です。

ラボユース生が作りたいものをサイボウズ・ラボの社員がメンターとしてサポートし、開発機材や開発活動に応じた補助金、旅費の援助をします。 開発物をオープンソースとして公開するという条件の元で著作権は開発者本人に帰属します。

発表会レポート

今年度は発表者が多いため、ごく簡単なコメントにて失礼します。 それぞれの発表概要や詳細については「若手エンジニアの育成と輩出を目的とするサイボウズ・ラボユースが創立10周年」のPDFの後半や各自の発表資料をごらんください。

集合写真
第10期サイボウズ・ラボユース成果発表会

低レイヤゼミ(担当:内田 公太)

○ 鈴木 友也 「xv6を自作VMM上で動かす」

KVMのAPIを利用してホスト型のVMMを実装した。 こうするとデバッグやデバイスドライバに専念できる。 基本的な命令、LAPICやディスクのエミュレーションをしてxv6を動かせた。 今後はベアメタル型のVMMの開発もしたい。

資料GitHub

○ 徳永 大貴 「USBデバイスドライバの実装」

キーボード、マウス、フラッシュドライブのデバイスドライバを開発した。 ホストコントローラHCを検出し、初期化してUSBドライバを見つけ、それからデバイス共通の初期化、個別の初期化をする。 デバイスとのやりとりはSCISコマンドで行う。

資料GitHub

○ 花井 一輝 「xv6にリモートファイルシステム9Pを実装する」

インターネットの機能に特化したOSを作りたくて教育用のOSとしてxv6を選択した。 TCPのステート管理が大変だったが性能もうまく改善できた。

資料GitHub

言語処理系ゼミ(担当:川合 秀実)

○ 菅原 大和 「複数アーキテクチャを対象とする自作言語コンパイラの設計とx86_64ツールチェーンの開発」

x86_64/AArch64コード生成ができるコンパイラを設計・実装(スタックマシン)した。 x86_64用のELFを生成するツールも作成した。 簡単に使えるためのリンカスクリプトジェネレータを開発中。

資料GitHub

○ 飯田 圭祐 「静的型付きスクリプト言語の開発」

スクリプト言語の利点と静的型付けをもつ言語を作ってみたかった。 func(arg)をarg.func()と書ける糖衣構文。fib(38)はRubyよりも速い。 以前から開発していたものを改良。GCの変更、イテレータやジェネリクスの実装。 2 level stack cachingの実装。今後は最適化をいろいろ試したい。

資料GitHub

○ 松井 誠泰 「OLAP SQLクエリの静的解析と自動並列化」

現状の問題点として機械学習システムではデータの依存関係の管理が難しい。 SQLの型解析は依存関係をもつクエリの実行や並列実行の型エラーを後になるまで検知できない。 この問題を依存関係を自動的に抽出する静的解析ツールによって解決したい。 最近はWiFiのチャンネル状態情報を利用した混雑検知の開発に関わっている。

資料GitHub

機械学習/自然言語処理に関するソフトウェア開発(担当:中谷 秀洋)

○ 笹田 大翔 「差分プライベート深層変換モデルによるテキストプライバシの保護」

差分プライバシーは有用性損失の抑制とプライバシーの両立を目指した。 ナレッジグラフによる一般化でノイズの付加量を低減した。

資料

○ 江畑 拓哉 「新進気鋭の深層学習モデル、Flow-based モデルで日本語口調変換を試みる」

対話システムにキャラクター性をもたせたい。そのキャラクターらしい口調にしたい。 文の潜在表現をベクトル化して、それを変換して元に戻すところにFlow-basedモデルを使ってみた。 ただしそのモデルは可逆であり、逆関数を実行できなければならない。 それで補助ライブラリを作り、そのあたりの研究を修士論文にした。

資料

インフラソフトウェア開発(担当:星野 喬)

○ 舛村 康成 「高競合状態における並行性制御法最適化の分析」

トランザクションの競合を解決する手法の一つBackoffの検証をした。 タイミング制御では説明しきれない状況。 そもそもsleepするthreadを減らせば競合の可能性を減らせるのではと考えて thread数をcontrolするとbackoffよりもよい効果がえられた。

資料

○ 山本 祥平 「DBMS を実装しながら学ぶ」

RDBMSの学習。undo loggingを実装。プログラムの書き方も学んだ。

資料

○ 和田 智優 「分散データシステムにおける非決定的性能バグの正確な解析」

複数のサーバが協調して動作する場合に発生するパフォーマンスバグPCBugの改善研究。 既存研究では実行パスに通らなかったバグは検出できないので記号実行を行った方法を提案する。 実行されないノンスケーラブルループへの条件を見つけて、既存ツールでは出来なかったバグを検知できた。

GitHub

C/C++によるソフトウェア開発(担当:光成 滋生)

○ 廣江 彩乃 「セキュリティログの解析を助ける視覚化ツール」

不正アクセスの視覚化ツールを見てセキュリティに興味を持ったので自分で作ってみたくなった。 sshのアクセスログを見てbingの地図APIを使ったどこから何回攻撃が来たかを可視化できるようにした。 GeoipでIPアドレスから緯度経度を取得する。

GitHub

○ 木下 和巳 「signalシステムコールによるホスト型ハイパーバイザー開発調査」

CPUで命令をし、センシティブな命令をsigactionでハンドリングする方法でハイパーバイザーが出来るか実験した。 問題がいろいろ起きたので一つずつ改善した。

資料GitHubblog

○ 美濃地 正貴「実行ファイルにおけるバイナリ差分アルゴリズム」

ソフトウェア更新のデータ通信量削減をしたい。 ファイルにパッチを当てて更新する。小さいパッチファイルを作りたい。 既存ツールbsdiffの紹介。バイナリ差分の特性をうまく使いbzip2で圧縮している。 Courgetteのコード読みや再実装中。

資料GitHub

○ 細谷 啓 「RISC-V CPU自作本の実装と執筆」

現代のCPU自作には膨大な知識が必要。 そこで高速化と機能拡張の二つを目指すCPUを作れるようになる指南書を執筆することにした。 Verilog-HDLやChisel, RSC-V, キャッシュ, パイプラインの解説を書いた。 Chiselが不安定で非自明なバグをいれてしまい再実装中。 6月には本としてなんとか出したい。

資料

まとめ

今年度も様々なテーマで活動されました。 今後もより一層の活動を願います。 ただ全てオンラインとなったため、横のつながりが少なかったのが残念です。 ラボユース生同士の交流を深めるのは今後の課題です。 11期生の応募も始まりました。興味ある方はぜひご応募ください。

「さよなら Flaky 。不安定なテストの探し方」というお話

$
0
0

みなさんこんにちは。サイボウズの三苫です。

本日は特にどこのイベントでも発表する予定もなく、実際に発表されなかった、不安定なテスト(Flaky Test)対策のお話をスライド & トークスクリプト形式で公開します。

不安定なテスト対策は、どこの現場でも継続的にされているかと思いますが私たちの一つの事例が皆様の対策の一助となれば幸いです。

さよなら Flaky 。不安定なテストの探し方

表題

皆さんこんにちは。サイボウズの三苫と申します。本日は「さよなら Flaky 。不安定なテストの探し方」というお話をします。

私たちのお悩みごと

私たちのお悩みごと

早速ですが私たちが抱えていた悩み、つまり前提となる課題からお話します。

サイボウズの kintone.com 基盤チーム(私の所属するチーム)はE2Eテストを使って AWS 上に構築した基盤上で kintone というサービスの動作保証をしようとしていました。

幸運にも kintone にはすでに多くのE2Eテストがあったからです。やったぞ、これが使えるぞと。

しかし実際にテストを流してみたところ、新しい基盤上では多くのE2Eテストが不安定になり基盤の不具合なのか単にテストが不安定なのか区別できずに困っていました。

これでは kintone の動作保証ができないぞと。

kintone というサービス

kintone というサービス

前置きなくお話をしていた kintoneというサービスですが、こちらはサイボウズの業務改善プラットフォームという分野のクラウドサービスです。

国内データセンターで cybozu.comというドメイン、AWS の US リージョンで kintone.comというドメインで複数環境で提供しています。

それぞれの環境はかなり異なっており、データセンターだけではなく利用されるミドルウェアも別のものが使われています。ロゴも違いますね。

kintoneの構成1

これがざっくりとした kintone の構成になります。

だいたいこんな感じで、kintone 本体のアプリケーションサーバーが複数のミドルウェアサービスやデータベースを利用しながら機能を提供するという感じになってるんですが…

kintoneの構成2

オンプレミス、つまり国内データセンターと AWS ではこの薄いオレンジ色で囲った部分が違うんですね。

ほとんどじゃないかと…

kintoneの構成3

さらに、細かいことを言い出すとアプリケーションサーバーもオンプレミスでは VM 上で動作しているんですが、AWSでは Docker コンテナとして動いているので、まぁかなりの部分が違います。

この二つは本当に同じ動作?

この二つは本当に同じ動作?

さて、そうなると気になるのがこの二つは本当に同じ動作なのかというところです。

中身が全然違うので不安になってきますよね。

どうやってこの二つの環境で同じ動作であることを保証したらいいでしょうか?

同じE2Eテストで外形的にテストします

同じE2Eテストで外形的にテストします

同じE2Eテストで外形的にテストすればよいでしょう。

そうすれば、少なくとも外から見える動作的には同じですというところまでは言えます。

E2Eテスト

E2Eテスト

ここまで、特に説明なくE2Eテストという用語を使ってきました。これはどういうテストでしょうか。

スライドではE2Eと表記していましたが、エンドツーエンド、つまり利用サイドから実際に処理をするサーバーサイドまで、端から端まで、ある部分だけをテストするのではなく全体をテストするという事です。

実際に動く環境を用意しユースケースの一連のシナリオを達成できるか検証します。

Webアプリケーションであればブラウザ操作を自動化してテストすることが多いかと思います。kintone では Seleniumを使っています。

E2Eテスト良いところ

E2Eテストの良いところ、悪いところ、どんなものがあるでしょう。まずは良いところから。

エンドユーザーの利用と同じ方法でテストできます。Webアプリケーションであればブラウザを使って動作を検証できます。

そして、内部のコンポーネントの状態はブラウザからは確認できませんので、純粋にユーザーから見た外部仕様をテストすることができます。

また、実際に動く環境を用意することになるので、各コンポーネントをすべて結合してユースケースが達成できるかどうか、その結合をテストすることができます。

E2Eテスト悪いところ

一方、悪いところです。

実際に動く環境を用意することは良い点もあるのですが、セットアップや実行に時間がかかることになります。

これは自動化されていても環境一式用意するとなると VM などのセットアップなども含めるとそれだけで数分のオーバーヘッドがかかってしまいます。

また、ブラウザの挙動やネットワークなどテストケースで十分に制御しきれない要素が出てくるのでテストが不安定になりがちです。

こういうものを俗に Flaky Test と呼ぶそうです。はがれやすい、もろいテストという事ですね。

このようなテストは最終的にデバッグ体験が最悪になりますので、問題が起きた時に修正するのがかなり厳しいという事になります。

テストが不安定だと何がまずいか

テストが不安定だと何がまずいか

良いところ、悪いところをお話しましたが、そもそもテストが不安定だと何がまずいでしょう?

まず、テスト全体の信頼性が失われてしまいます。成功するまでテストを再実行するとかですね。

「先輩、テストが落ちるんですが?」

「信心が足りないね。心を込めて再実行しなさい」

など神がかった現場、皆様に覚えはないでしょうか。

次に、テストの成否そのものが次第に無視されるようになります。このテストは失敗しても気にしなくていいよ、とか甘い言葉に誘われて次第にテストの結果を気にしなくなります。

不安定だからと修正しても、直ったのか偶然テストがパスしたのか区別できなくなります。10回パスしたから大丈夫だろと思っても、大丈夫じゃなかったりします。

ここまでは単にテストだけの問題なのですが、テスト全体の信頼性が損なわれることにより潜在的な不具合が見過ごされてしまうという事が起きてしまいます。

たまに失敗するテストケースがロジックのコーナーケースを踏んでたりする場合もありますね。日曜日だけ落ちるとか、月初だけ落ちるとか。

こういう、検出できていたものを見過ごしてしまうと非常によろしくないです。

不安定なテストの探し方

不安定なテストの探し方

テストが不安定だとまずい事がわかりました。では、不安定なテストをどうやって探しましょう。これがなかなか難しい。

なぜかというと、一回のテスト結果だけでを不安定かどうかを判断できないからですね。一回のテスト結果は成功か失敗かのどちらかです。複数回の結果を見て不安定かどうかを判断する必要があります。

例えば、あるテストケースがコミットごとに実行されるとして 5 回のテスト結果を見るとしましょう。

この時、例えば失敗の回数に着目するとどちらも 5 回中 3 回失敗しています。

ですが Case1 のように、2回連続して成功し、その後3回失敗しているという場合は不安定というよりもどこかで失敗するテストになったと考えられます。

ここで、変化に着目すると Case2 では、5回中3回失敗という意味では Case1 と同じですが、テスト結果が変わりうるタイミング4回のうち4回。すべてで変わっています。こちらは不安定になったのではないかと考えられます。

このように、同一ケースの複数のテスト結果を時系列で並べてみると不安定かどうかがわかりやすいですね。

テストの実行履歴とか残してます?

テストの実行履歴とか残してます?

これで不安定なテストの探し方がわかりました。探し方がわかりましたがどうやって私たちの現場で調べましょう。テストの実行履歴を残して、今言った指標を簡単に参照できるようになってるでしょうか。

代表的な CI / CD ツールである Jenkins や CircleCI の実行履歴の場合、ビルドごとにテスト結果を見る必要があります。 あるテストケースの傾向という軸でみるための UI がないんですね。もしかしたらプラグインなど工夫すれば見られるかもしれませんが、すみません、そこは私も不勉強なのでわかっていません。

不安定なテストケースの場合、テストケースごとの成功・失敗の頻度を見る必要があります。そして、失敗した時は不安定になった原因は様々ですのでその場でログを見て確認できるようになっていて欲しいです。

さて、そういう要件にピッタリ合うようなプロダクトやソリューションはあるでしょうか。

ReportPortal

ReportPortal

私たちのチームは ReportPortalというソフトウェアを利用しています。

これは、自動テストの結果を記録してダッシュボードを作るためのソフトウェアで、テスト結果を様々な軸から分析することができます。

CI やローカルでテストの実行して生成される JUnit XML 形式の結果をテスト実行中や完了後に ReportPortal に送信して一覧・分析することができます。

また、多数のテストフレームワークとのインテグレーションにも対応しています。

他にも機械学習とか便利な機能も多くあるようですが、私たちはまだ単純なレポーティングの用途でしか使用していません。

不安定なテストの分析

不安定なテストの分析

さて、実際に ReportPortal を利用した例を見てみましょう。

これは kintone に存在するE2Eテストで不安定なテストや失敗の多いテストをレポートしたものです。

ここで API というのは kintone のサーバーが提供している API を JUnit テストケースから呼び出してテストするテストスイートで、UI というのはブラウザを用いたテストのテストスイートになります。これをそれぞれランキング形式で表にしています。

テストケース名などは出せないのでモザイクになってしまっていますが、どのようなテストケースが不安定か、たくさん落ちているかがわかります。

このようにランキング形式で出すことで不安定の原因になっているテストを効率的に修正することができますので、E2Eテストの信頼性を高めていくことができます。

テストの実行履歴

テストの実行履歴

次に問題を修正するためにあるテストケースの詳細を見る画面です。 修正に取り掛かる際、そのテストケースの失敗の傾向やログを把握したくなるかと思います。 この例ではビルド #1321 から失敗し始めていて、その前のビルド #1320 でも実行時間が伸びているのがわかりますね。 時計マークの +1719% というのが実行時間が伸びているということです。 また、テスト実行時のログも下の画面、こちらもモザイクかけてしまっているのですが参照する事ができます。

この辺りの情報を合わせてみると原因が特定しやすくなってきます。

私たちのビフォー・アフター

私たちのビフォー・アフター

ReportPortal を導入した私たちのビフォー・アフターです。

ビフォーでは、経験をもとにテストが不安定かどうか判定して再実行や修正を行っていました。 詳細なテスト結果はログから追う必要があるので必要になるまで確認しない、腰が重い状態でした。

アフターでは、不安定なテストを実績をもとに判断できるようになりました。 これによって、明らかなバグか不安定で落ちやすいかの区別がつくようになり、修正する効果の大きなテストから修正していけるようになりました。

まとめ

まとめ

それでは、本日のお話のまとめです。

E2Eテストは不安定になりやすく特定したりデバッグしにくいという課題を、私たちの事例をもとにお話しました。

不安定なテストを探すには、テストの実行履歴を用いて分析すると特定しやすくなります。

そして、最後に私たちが不安定なテスト探しに活用している ReportPortal というツールの紹介を行いました。

おしまい

おしまい

以上、「さよなら Flaky 。不安定なテストの探し方」というお話でした。 最後までお読みいただきありがとうございました。

私たちのチームは DevOpsエンジニア(AWS版kintone)としてキャリア採用を募集しています。ご興味ある方はご参照ください。

エンジニアインターンシップ2021を開催します!

$
0
0

こんにちは!エンジニアインターン運営チームです。

サイボウズでは毎年夏に、エンジニア/デザイナー向けサマーインターンシップを開催しています。 今年も昨年と同様にフルリモートでの開催を予定しております。

インターン2021ロゴ

製品開発を行うチームはもちろん、製品やチームの基盤を支えるチームのコースもご用意しました。 きっと自分に合うコースが見つかると思います。

また各コースのコンテンツ以外にも、社長とのランチ会に参加したり、気になるチームの社員を指名して、1対1で話したりできる時間もご用意しています。

全国各地からのエントリーをお待ちしています!

対象

  • 2023年4月までに入社可能

エントリー開始日

2021年4月26日(月)より随時エントリー受付開始

https://cybozu.co.jp/company/job/recruitment/intern/2021/

開催コース

  • Webサービス開発(kintone)
  • モバイルアプリ開発
  • デザイン&リサーチ
  • フロントエンドエキスパート new!
  • DevOpsエンジニア(AWS版kintone) new!
  • Kubernetes基盤開発 new!
  • 生産性向上 new!
  • 品質保証
  • プロダクトセキュリティ

各コースの内容、応募方法や報酬などの詳細は特設サイトをご確認ください。 cybozu.co.jp※コースによって開催時期や応募期間が異なりますのでご注意ください。

参考情報

昨年までのインターンの報告も投稿しておりますので、ぜひご覧ください。 blog.cybozu.io

去年の様子
去年のようす

モバイルチームの働き方と取り組みの紹介

$
0
0

こんにちは!モバイルチームの森嶋です。

今年5月にモバイルエンジニアとしてサイボウズへ中途入社しました。

この記事ではモバイルチームの基本的な働き方と、私がサイボウズに入社してモバイルチームとして働くようになった中で「これは良い取り組みだ」と感じた点をいくつか紹介させていただきたいと思います。

モバイルチームに関しては下記の記事をご参照ください。 blog.cybozu.io

スケジュール

上記の記事で紹介されているように、モバイルチームが担当しているプロダクトは複数あり、私が担当するプロダクトの開発ではスクラム開発を採用しています。

1週間のスプリントで開発を行っており、木曜から開発が始まり水曜にそれをReview(受け入れ)してふりかえりを行うという流れです。(プロダクトごとに異なります)

スケジュール
私が担当するプロダクトの1週間の基本スケジュールです。

開発の進め方

毎日10:15-18:00の間で、開発チームの予定が入っていない時間はZoom接続しながらモブプログラミングを行っています。 チームのメンバーが会議や所用で離席している時など、モブ作業を進めることが出来ない時間は個人の学習・探求をそれぞれ行っています。

取り組み紹介

ここからは、モバイルチームが行っている取り組みについて紹介していきます。

取り組み紹介 - 技術系MTG

(毎週開催)

モバイルチームでは複数のプロダクトを扱っているので、各プロダクトで得た知見やハマりポイントを共有する会です。

それ以外にもプロダクトに行った改善の紹介や、プロダクト開発で出会った困りごとや疑問の相談など様々な情報を共有しています。 普段プロダクト毎に分かれて開発しているので、プロダクトを横断して気軽に技術の話ができる場となっています。

取り組み紹介 - ウォッチスレを見ながらワイワイ会

(隔週開催)

各個人が気になった情報を共有するスレッドがあり、そこに書かれているものをチームみんなで見ながらわいわい話をする会です。

他社テックブログで面白いと思った記事や、Swiftの次期バージョンで採択されるプロポーザルの話題、Kotlinのロードマップ、AirTagどう?のような話題まで様々なモバイル界隈の知識をここでチームに共有しています。

取り組み紹介 - 1onN

(毎週開催)

個人の成長支援や活動改善のために個人にフォーカスして話をしています。

3人〜4人程度の小グループに別れて、話す人を決めてから他の人を壁打ち相手に話したいことを話す時間です。

1人の話を複数人で聞く場を作ることで、仕事上で接点の少ないメンバーとの交流にも繋がり、特にメンバーの多数が在宅勤務の働き方になってからはチーム内のコミュニケーション不足解消の一助となっています。

取り組み紹介 - 時間割ふりかえり

(月1開催)

時間配分を定期的に見直して、想定通りの時間配分になるようにすることが目的の会です。

ついついプロダクト開発ばかりになりがちで、改善タスクや技術探求に時間を避けないことが起こりがちなので、月に1回各個人の作業時間割合を見える化し、理想の割合となっているかを見直しています。

時間割合ふりかえり
ふりかえり時に利用している表のサンプルです。ヒートマップで見える化を行っています。

取り組み紹介 - モ会

(毎週開催)

「モTASK」の進捗や行動をふりかえったり、チームで話したいことについての議論、情報共有、その他ざつだんをしています。

「モTASK」とは、モバイルチームの目標に沿って各メンバーがグループで取り組むことをタスク化したものです。これらのタスクはチームメンバー自身が自ら希望して作成したものとなっており、誰がどのタスクにJoinするかはメンバー自身が選択しており、掛け持ちも可能です。

モTASKサンプル
モTASKの一例です。弊社kintoneアプリを利用して管理をおこなっています。

おわりに

チームの技術に対する学習取り組みや情報共有などはどのチームでも課題になりがちだと思うので、それがきちんと仕組みとしてチームで取り組まれるようになっていること。

そしてその取り組みをきちんと見える化して検査することが出来ていることが、何よりも素晴らしいと感じました。(見える化大事!)

最後までお読みいただきありがとうございます。

私たちのチームでは、 モバイルエンジニアを募集しています。

ご興味ある方はご参照ください。

MySQL 8.0 への移行が完了しました ~さようなら全ての MySQL 5.7~

$
0
0

こんにちは。クラウド運用チームの飯塚です。

私たちは cybozu.com 本番環境の MySQL を昨年末から順次 8.0 系へアップグレードしており、前回の定期メンテナンスにおいて全てのインスタンスのアップグレードを完了しました。この記事では、私たちが MySQL 8.0 への移行に取り組んだ理由と必要になった対応について紹介します。

なぜ MySQL 8.0 へ移行したのか

MySQL は当社が提供しているクラウドサービスの多くがプライマリデータストアとして利用しており、この記事の執筆時点で400を超える数のインスタンスが本番環境で運用されています。私たちはこれら全てを MySQL 8.0 へ移行しました。

一般的にデータベースのアップグレードは事前の検証やコードの修正に大きな労力が必要であり、敬遠されることも多い作業です。コストとの兼ね合いから EOL の直前まで後回しにされるような事例も珍しくありません。一方、MySQL 8.0 ではさまざまな改善が行われており、そのいくつかはアップグレードのためのコストをかけるのに値するものであると判断しました。特に、以下の点は当社のビジネスにおいて大きな価値があるものだと考えています。

  • GTID-based レプリケーションにおける制限が緩和された
  • MySQL の再起動時に AUTO_INCREMENT のカウンタが巻き戻る問題が解消された

これらについて解説します。

GTID-based レプリケーションにおける制限の緩和

以下の記事でも紹介したように、当社が提供しているサービスでは長い間レプリケーションを利用していませんでした。

blog.cybozu.io

そのため、レプリケーション構成へ移行するための調査を始めてみるといくつか問題が見つかりました。GTID を使ったレプリケーションには CREATE/DROP TEMPORARY TABLEによるテンポラリテーブルの操作や CREATE TABLE ... SELECTステートメントが利用できないといった制限がある一方で、当社のサービスの中には CREATE TEMPORARY TABLEに強く依存した処理がいくつかあり、このままではレプリケーション構成へ移行することが難しいということがわかりました。

これらの CREATE TEMPORARY TABLEに関する制限は MySQL 8.0.13 で、CREATE TABLE ... SELECTに関する制限は MySQL 8.0.21 で取り除かれ、GTID を使ったレプリケーションでもこれらのステートメントが利用できるようになりました。

MySQL 8.0 Reference Manual :: 17.1.3.7 Restrictions on Replication with GTIDs

MySQL 8.0 へのアップグレードが完了したことによって GTID を使ったレプリケーションへの対応も完了したことになるため、当社のサービスの可用性を高めるための取り組みを大きく進展させることができたと考えています。

再起動時に AUTO_INCREMENT のカウンタが巻き戻る問題の解消

MySQL に遥か昔から存在する有名な問題として、再起動時に AUTO_INCREMENT のカウンタが巻き戻るというものがありました (Bug #199)。この問題はあまりにも長い間存在していたため MySQL の残念な 仕様のひとつと考えられることも多いものでしたが、MySQL 8.0 になって修正されました。

MySQL 8.0 Reference Manual :: 15.6.1.6 AUTO_INCREMENT Handling in InnoDB

この問題の例を以下に示します。t1 というテーブルに AUTO_INCREMENT によって採番された 1, 2, 3 という値を入れた後、3 のレコードを削除します。

CREATETABLE t1 (c1 INT PRIMARY KEY AUTO_INCREMENT);
BEGIN;
INSERTINTO t1 () VALUES (), (), ();
DELETEFROM t1 WHERE c1 = 3;
COMMIT;

このとき、3 が削除されたとしても次に採番される値は4になります。

mysql> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `c1` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.00 sec)

しかしながら、MySQL を再起動すると MySQL 5.7 では次に採番される値が巻き戻って3になります。これは再起動時に次に採番される値を AUTO_INCREMENT が設定されているカラムの最大値+1で初期化しているためです。

mysql> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `c1` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.00 sec)

この問題が修正されたことで大規模なテーブルを安全にマイグレーションしたい場合に対応できる事例が増えました。大規模なテーブルの安全なマイグレーションに関する取り組みについては以下の記事もご覧ください。

blog.cybozu.io

実際に対応が必要だった MySQL 8.0 の変更点

MySQL 8.0 では互換性のない変更もかなり含まれます。私たちは MySQL 8.0.0 から MySQL 8.0.20 までのリリースノートにはすべて目を通し、対応が必要かどうか確認していました。

f:id:cybozuinsideout:20210520171542p:plain
リリースノートの内容を精査するタスクの管理には kintone を使いました

その中にはコードの修正が必要になったものもいくつかあります。特に影響が大きかったものを抜粋して紹介します。

utf8mb4 の照合順序のデフォルト値の変更

文字コードとして utf8mb4 を使っている場合の照合順序のデフォルト値が utf8mb4_general_ci から utf8mb4_0900_ai_ci に変更されました。

当初は utf8mb4_general_ci を明示的に指定するだけで対応できるかと考えていたのですが、mysqldump で MySQL 5.7 のテーブルを抜き出して MySQL 8.0 にリストアしようとした場合には問題が起こることが分かりました。MySQL 5.7 での utf8mb4 のデフォルトの照合順序は utf8mb4_general_ci であるため、たとえテーブル作成時に明示的に utf8mb4_general_ci を指定していたとしても mysqldump で抜き出したときには utf8mb4_general_ci の指定が消えてしまい、そのまま MySQL 8.0 でリストアすると utf8mb4_0900_ai_ci に変更されてしまいます。

CREATETABLE t1 (
  c1 VARCHAR (140) NOTNULL
) DEFAULTCHARACTERSET utf8mb4 COLLATE utf8mb4_general_ci;
$ mysqldump --compact d1/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATETABLE `t1` (
  `c1` varchar(140) NOTNULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
$

当社が提供している SaaS 製品は申し込みがある度にテナントごとのテーブルが作成される仕組みであり、本番環境で日常的にテーブルが新規作成されるため、CREATE TABLE するための SQL ファイルを準備しておく必要があります。mysqldump を使ってこのファイルを作成している場合には元となる MySQL を 8.0 にアップグレードしておくという対応が必要になりましたが、移行途中の時期には混乱がありました。

ところで MySQL 8.0 では default_collation_for_utf8mb4 の設定によってデフォルトの照合順序を utf8mb4_general_ci に戻すことができます。しかしながら私たちの用途ではあまり使い勝手が良くなく、この方法は採用できませんでした。

まず、default_collation_for_utf8mb4 はオンラインで変更可能なパラメータですが、驚くべきことに my.cnf に設定を書くことができません。この挙動は不具合ではないかということでバグ報告が行われていますが仕様であるとして Close されています。

Bug #96335 MySQL doesn't start when I set default_collation_for_utf8mb4 in my.cnf

my.cnf に設定を書くことができない問題の回避策として MySQL 8.0 の新機能である SET PERSIST ステートメントを使う方法があります。これは MySQL のシェルだけを使って設定項目を永続化(再起動後にも有効に)することができる機能です。my.cnf では default_collation_for_utf8mb4 を設定することができませんが、SET PERSIST では設定することができ、再起動後も設定を引き継ぐことができます。

ただし、この回避策にも注意点があります。永続化された設定はテキストファイルとして書き出されますが /etc/mysqlのような設定ファイル用のディレクトリではなく /var/lib/mysqlのような データディレクトリに書き出されます。これは私たちがこれまで利用してきた設定ファイルを自動でデプロイする仕組みと相性が良くなく、SET PERSIST の利用は見送られました。

SQL_CALC_FOUND_ROWS と FOUND_ROWS() が deprecated に

MySQL 8.0.17 で SQL_CALC_FOUND_ROWS と FOUND_ROWS() が deprecatedになりました。この機能は LIMIT 句を付けたクエリについて LIMIT 句を付けなかった場合の行数が得られる機能で、ページャーの実装において全体の件数を取得したい場合に使われることがあります。たとえば、全体で10行あるテーブル t1 について以下のようなクエリを実行すると先頭の5行のレコードに加えて FOUND_ROWS() の結果として 10 が得られます。

BEGIN;
SELECT SQL_CALC_FOUND_ROWS * FROM t1 LIMIT 5;
SELECT FOUND_ROWS();

置き換え先としては SQL_CALC_FOUND_ROWS を付けない SELECT を実行した後に、同様の条件で LIMIT を付けない SELECT COUNT(*) を実行する方法が紹介されています。

BEGIN;
SELECT * FROM t1 LIMIT 5;
SELECTCOUNT(*) FROM t1;

COUNT(*) を使う方法は SQL_CALC_FOUND_ROWS を使う方法と比較していくつかの最適化が有効になり高速になるとされています。トランザクション分離レベルに REPEATABLE-READ 以上を用いている場合は COUNT(*) に単純に置き換えることができます。しかしながら、トランザクション分離レベルが READ-COMMITTED 以下の場合は単純に置き換えることができません。これは、SQL_CALC_FOUND_ROWS を用いた件数取得は別のトランザクションによる件数変更の影響を受けない一方で、COUNT(*) を用いた件数取得はファジーリードやファントムリードの影響を受けて SELECT ... LIMIT Nの実行時と結果が異なる場合があるためです。

Connector/J のメタデータ取得処理の性能低下

Connector/J にはテーブルのカラム名などのメタデータを取得する機能があります。この処理は MySQL 8.0.3 未満であれば SHOW FULL COLUMNS を使い、MySQL 8.0.3 以上であれば INFORMATION_SCHEMA.COLUMNS を使うような使い分けで実装されています。使い分けの理由は MySQL 8.0 では information_schema のほうが効率的であるためと説明されています。

このメタデータ取得の処理において MySQL 8.0.21 未満のバージョンでは以下のようなパフォーマンス問題が報告されており、MySQL 5.7 と比べて SHOW FULL COLUMNS, SHOW FIELDS, SHOW TABLES などのパフォーマンスが低下している状況でした。

Bug #98750 SHOW FIELDS command regression on MySQL 8.0

この問題により、Connector/J のメタデータ取得機能を利用しているアプリケーションについて10%を超える性能低下が検出されました。最終的に私たちは MySQL 8.0.21 未満のバージョンを利用することにしたため、メタデータの取得を回避するようにコードの修正が必要でした。

sys.innodb_lock_waits の罠

MySQL でロック競合の状態を解析しようとしたとき、以下のブログの記事で紹介されているクエリを参考にしたことがある方も多いのではないでしょうか?

sh2.hatenablog.jp

MySQL 8.0 ではこのクエリで使われている information_schema のテーブルの一部が廃止され、似たような機能を持つテーブルが performance_schema に追加されました。概ね、information_schema.innodb_lock_waits を performance_schema.data_lock_waits に、information_schema.innodb_locks を performance_schema.data_locks に置き換えることになります。

当社ではロックに関わる問題を検出して性能改善に役立てるため、ロック解析のクエリを定期実行してログに記録しています。しかしながら、information_schema のテーブルの drop-in replacement として performance_schema のテーブルを使うことには注意が必要です。私たちは以下のようなパフォーマンス問題を発見して不具合として報告しました。

Bug #100537 Performance degradation caused by monitoring sys.innodb_lock_waits in MySQL 8.0

報告したテストケースでは sys.innodb_lock_waits(ロック解析に使われるテーブルの違いを吸収して透過的に扱うことができる sys スキーマのビュー)を出力するクエリを1秒間隔で実行するモニターを設定したうえで、ロック待ちが多数発生するようなトランザクションを実行しています。MySQL のバージョンとモニターの有無によって実行時間に次のような違いが現れました。

MySQL のバージョン モニター無効 モニター有効
MySQL 5.7 12.853 秒 13.680 秒
MySQL 8.0 12.629 秒 110.889 秒

モニター有効化による性能の低下は MySQL 5.7 では10%未満であり、モニターから得られる情報の価値を考慮すると許容できるものでした。一方、MySQL 8.0 ではモニター有効化によって処理時間が10倍近くになっており、許容することができない性能低下が発生してしまいました。

この問題は根本的な対処方法が見つけられておらず、モニタリング用のクエリの実行間隔を伸ばしたり採取する情報を減らすことで対応しています。もし同様の使い方をしていて困っている方がいればぜひ Bug #100537の Affects Me を押していただけると幸いです。

外部キー制約をもつテーブルの DDL 操作によるメタデータロックの発生

MySQL 8.0.3 の 新機能として外部キー制約をもつテーブルの DDL 操作をする際に参照先のテーブルのメタデータロックを獲得するようになりました (WL#6049)。この挙動については以下の記事でも紹介されています。

yoku0825.blogspot.com

私たちは以下のような利用パターンで MySQL 5.7 との挙動の違いに悩まされました。既に t1 というテーブルが存在していてサービスを運用している状況で、外部キー制約で t1 を参照する新しいテーブル t2 を作成することを考えます。

まずは親テーブルを作成します。

CREATETABLE t1 (c1 INT PRIMARY KEY);

t1 を SELECT するだけのトランザクションを作っておきます。このとき t1 に対するメタデータロックが獲得されます。

BEGIN;
SELECT * FROM t1;

別のトランザクションで新しいテーブル t2 を作成します。このテーブルは外部キー制約で t1 を参照します。MySQL 8.0 ではこのとき t1 のメタデータロックを獲得するようになったため、先ほどの t1 を SELECT するだけのトランザクションによって CREATE TABLE がブロックされます。

CREATETABLE t2 (c1 INT, c2 INT, FOREIGN KEY (c1) REFERENCES t1 (c1));
*************************** 3. row ***************************
     Id: 2227
   User: root
   Host: localhost
     db: d1
Command: Query
   Time: 10
  State: Waiting for table metadata lock
   Info: CREATE TABLE t2 (c1 INT, c2 INT, FOREIGN KEY (c1) REFERENCES t1 (c1))

この挙動は日次の集計処理など比較的長時間の SELECT を実行するトランザクションと、オンラインで実施可能であるはずのテーブル追加が同時に行えない(設定によってはタイムアウトする)ことを意味します。しかしながら、この挙動は不具合ではなく整合性を保つための望ましい変更であると思われるので、運用で回避するしかありません。リリースの頻度が増えてきた場合にはこの制限によって生じる問題が顕在化してくる恐れもあるため、上手な付き合い方を模索していきたいと考えています。

トランザクション分離レベルを設定する変数名の変更

MySQL 8.0 になり、変数名が変わったものがあります。私たちを悩ませたのはトランザクション分離レベルを表す tx_isolation が transaction_isolation に変わったことでした。transaction_isolation は MySQL 5.7.20 で tx_isolation のエイリアスとして導入され、tx_isolation は MySQL 8.0.3 で削除されました。すなわち MySQL 5.7.20 未満から MySQL 8.0.3 以上にアップグレードしようとした場合には中間的な移行措置なしで変数名を切り替える必要があります。

私たちは MySQL に接続する際のデータソース名 (DSN) のオプションとして以下のように tx_isolation を指定している箇所があり、どうすれば円滑に変数名を切り替えることができるか悩んでいました。

user@tcp(172.16.0.1)/?tx_isolation=%27READ-COMMITTED%27

MySQL のバージョンによって変数名を変えるような分岐を作りたくなりますが、利用しているコネクターのライブラリによってはより良い方法が使える場合があります。たとえば Go の database/sqlなら BeginTxの引数でトランザクション分離レベルを指定することができます。こちらは変数名の変更の影響を受けません。そもそもトランザクション分離レベルはトランザクション単位で個別に設定されるものであるため、こちらの使い方のほうがコネクション単位でグローバルに設定するよりも望ましいかもしれません。

その他対応が必要だった仕様変更

上記以外にも以下の変更についてはコードの修正が必要となりました。リリースノートの焼き直しとなってしまうため詳細な解説は省きます。

UNION 句と FOR UPDATE を使う場合に括弧が追加で必要

Bug #99561で報告されたものです。MySQL 8.0 で SQL のパーサーが書き直されたことによる影響です。

Implicit Account Creation の廃止

MySQL 5.7 まで使うことができた、GRANT ステートメントでユーザーが作成される仕様です。MySQL 8.0.11 で削除されました。

information_schema のカラム名が大文字固定に

MySQL 5.7 以前では information_schema を SELECT するとき、ヘッダに書かれたカラム名の大文字・小文字が SELECT 句に書いたものに追従するという不思議な仕様がありました。

mysql> SELECT table_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'd1';
+------------+
| table_NAME |
+------------+
| t1         |
+------------+
1 row in set (0.00 sec)

この仕様は MySQL 8.0 で削除され、information_schema のカラム名は大文字に固定されました。

EXPLAIN ステートメントの EXTENDED キーワードの廃止

EXTENDED を付けなくても常に EXTENDED 相当の情報が表示されるようになったためです。MySQL 8.0.12 で利用できなくなりました

GROUP BY a ASC の廃止

GROUP BY a ASCと書くと GROUP BY a ORDER BY a ASCと書いたのと同じ結果が得られるという仕様です。MySQL 8.0.13 で削除されました。コードの変更量だけを考えれば、MySQL 8.0 の対応に必要なコードの修正の中でこの仕様変更に起因する修正が最も多かったチームもあるようです。

おわりに

この記事では当社のサービスで利用している MySQL を 8.0 系へアップグレードした理由と必要になった対応について紹介しました。一般的に言われているように事前の検証やコードの修正には労力が必要でしたが、cybozu.com が MySQL 8.0 になったことで機能追加や制限の緩和によるさまざまな恩恵を受けることができるようになりました。この成果はサービスの安定性の向上やメンテナンス時間の短縮といった形でお客さまに還元していきたいと思います。

MySQL 8.0 系では継続的デリバリーモデルによってマイナーバージョンアップでも機能が追加されたり仕様が変更されたりすることがあるため扱いにくいという声もよく聞かれます。しかしながら、追加された機能には魅力的なものも多く、そのいくつかはアップグレードする動機になり得るものです。この記事の中に MySQL 8.0 系へのアップグレードを検討されている方のお役に立てるものがあれば幸いです。

最後になりましたが MySQL コミュニティのみなさまが共有して下さっている導入事例や不具合情報がなければ MySQL 8.0 へのアップグレードを完遂することはできませんでした。世界中の MySQL コミュニティのみなさまに感謝申し上げます。


Renovate による依存関係更新フロー改善ことはじめ

$
0
0

こんにちは、生産性向上チームの平木場 (@Shitimi_613) です。みなさん依存関係更新は定期的にやっていますか?今回は生産性向上チームが開発した assam という OSS の依存関係更新フローを改善したお話をします。


目次


はじめに

生産性向上チームは assamという OSS を開発およびメンテナンスしています1。必要な機能の開発はすでに終えており、現在は問題が発生した場合に bug fix や脆弱性改修をリリースしています。

そのため、リリースは頻繁に行っているわけではなく、assam のコードに触れるときは何か問題が起きたときだけです。その結果、リリースの間隔が開くことによって、いざリリースしようとしたときに依存関係の更新で様々な問題に遭遇するというつらい問題が発生していました。

今回は細かく依存関係更新をしていくために実施した取り組みを紹介します。

こまめに依存関係を更新していくために

こまめに依存関係を更新していくためには、「なにか問題が起きたら修正してリリースする」という非常にざっくりとしたメンテナンスのルールを刷新する必要がありました。まず要求をチームメンバーで出し合い、要求を満たすためにやることを考えました。

依存関係更新がこまめにできていない原因として以下の事柄が挙げられました。

  • 依存関係をいつ更新するかが定まっていない
  • 依存関係更新のプルリクエストを作成するサービス(Renovate)がプルリクエストを作っても放置して忘れてしまう
  • 更新時に何を確認するべきかが定まっていない、属人化されている(ユニットテストが通ればいい?実機でのテストも必要?)

要求

原因を解決するために必要なことを話し合い、こまめに依存関係を更新していくための要求を出しました。

要求理由
依存関係を定期的に更新する不定期で発生するリリース作業を楽にするため
依存関係の更新のみを理由にリリースしない意味のない更新はユーザーに価値を提供しないため
更新作業になるべく工数を割かない他の作業ができる時間が減ってしまうため
動作確認を忘れない更新に伴って品質を落としたくないため
更新作業の存在を忘れない今回のような問題を再発させないため

決めた取り組み

そして、要求を満たすために以下を取り組むことにしました。

取り組み満たす要求
週一で更新作業を行う「依存関係を定期的に更新する」
動作確認手順書を作成する 「更新作業になるべく工数を割かない」「動作確認を忘れない」「依存関係の更新のみを理由にリリースしない」
依存関係更新のプルリクエストが自動生成されるようにする「更新作業になるべく工数を割かない」
チームで使っているタスク管理ツールに定期的に更新タスクが自動生成されるようにする「更新作業の存在を忘れない」

更新作業の間隔についてどれくらいの間隔が適切かの判断が難しかったため、とりあえず週一で行ってみて必要なら間隔を変えることにしました。

実際にやったこと

実際にやったことが以下になります。

  • 動作確認手順書の作成
  • Renovate 設定ファイルの修正
  • 更新タスクの自動生成

動作確認手順書の作成

assam の動作確認を円滑に行うために動作確認手順書を作成しました。(本当は動作確認そのものを自動化したいところですが、動作確認を自動化するために専用の環境を用意するのが厳しく、また確認したい項目が多くなかったため、手動で動作確認をすることにしました。)

動作確認手順書には「手順」と「確認したいこと」をそれぞれ含めました。また実際に実行するコマンドを記述することで、高速かつ人によってムラが出ない動作確認を行えるようにしました。

動作確認手順書を一部抜粋したものを以下に示します。


  • 確認したいこと
    • ビルドできること
    • 設定ができること
    • 認証ができること
    • 認証後に AWS CLI が叩けること
  • 手順
    1. プルリクエストの Files Changed を見て不自然な差分がないことを確認する
    2. チェックアウトする
    3. ビルドする(go build(確認:ビルドできること)
    4. ~/.awsを rename する (mv ~/.aws ~/.aws.backup)
    5. assam の設定をする(./assam -c(確認:設定ができること)
    6. ...

Renovate 設定ファイルの修正

assam は依存関係更新をスムーズに行うために Renovateを以前から利用しており、依存関係先が新たに更新されるとその都度プルリクエストが作成されます。

私たちは 1 週間に一度のみ更新することにしたため、プルリクエストが生成されるタイミングを週一にするような設定をしました。

また、このままでは依存関係ごとにプルリクエストが生成されるため、検証・更新を複数回行う必要があり煩雑です。assam の場合、依存関係の数が少ないため、1 つの Pull Request にまとめても問題発生時の切り分けは難しくありません。そのため、複数の依存関係の更新を 1 つのプルリクエストにまとめる設定も行いました。

実際のrenovate.json5を以下に示します2

{"extends": [":label(renovate)", // プルリクエストに renovate ラベルを付与する":prConcurrentLimit10", // Renovate が作れる open なプルリクエストを最大 10 個にする":timezone(Asia/Tokyo)", // タイムゾーンを Asia/Tokyo にする":enableVulnerabilityAlertsWithLabel(security)", // 脆弱性に関するプルリクエストに security ラベルを付与する":semanticCommitTypeAll(chore)", // コミットメッセージの先頭に chore を付与する"schedule:weekly" // 毎週月曜日の午前 0:00 から午前 3:00 までの間でプルリクエストを作成する],
  "postUpdateOptions": ["gomodTidy" // go mod tidy を行うようにする],
  "groupName": "all" // 全てのプルリクエストを all という名前でまとめる}

この設定により、依存関係更新のコミットをまとめたプルリクエストが毎週月曜日に生成されます。生成されるプルリクエスト例を以下に示します。

Renovateで生成される依存関係更新コミットがまとまったプルリクエスト例
Renovateで生成される依存関係更新コミットがまとまったプルリクエスト例(https://github.com/cybozu/assam/pull/181)

更新タスクの自動生成

生産性向上チームは Linearというタスク管理クラウドサービスを利用しています。Linear には API が用意されており、Issue(タスクのこと)の生成やメンバーのアサイン、State の変更などを自動化できます。

今回私たちは GitHub Actions ワークフローを定期実行し、Linear API を叩き、タスクを自動生成するようにしました。Scheduled eventsを設定することでワークフローを自動実行できます3

例えば、毎週月曜日の朝 9 時(日本時間)に実行したい場合は以下のようにします。(GitHub Actions では UTC 時間で cron 構文を書く必要があるので 9 時間ずらして設定しています。)

on:schedule:- cron:'0 0 * * 1'

Linear API をワークフローのシェルから直接叩く方法が手っ取り早かったのですが、Issue の description を Markdown で記述する必要があるため、今後、定期作成タスクの新規作成や編集の際に開発体験がよくないと考えました。そのため、Linear JavaScript SDK を使って Markdown で書いたタスクを作成するプログラムを作成し、定期作成タスクの編集や新規作成をしやすくしました。

例えば、次のような Markdown を作成します。

---# YAML front matter に Linear API の IssueCreateInput のパラメータを記述することで、Issue 登録時にその情報が反映されるようになっています。(title は必須です)
##参考:https://github.com/linear/linear/blob/19a8db2837cc7e324891e83241b68ce95405fba3/packages/sdk/src/schema.graphql#L2004

title: assam の renovate 対応
estimate: 1
---
https://github.com/cybozu/assam/pulls

##タスク作成時のチェック
-このタスクのユーザと提供価値、それに対する品質の確認方法が決まっている
    -ユーザ: 生産性向上チーム
    -提供価値: assam のリリースするときに依存関係更新で困らない
    -それに対する品質の確認方法: 動作確認できている

##受け入れ条件
- [ ] assam の renovate が作成した PR がすべてマージされている
- [] [動作確認マニュアル](動作確認マニュアルへのリンク)の手順で動作確認ができている

##やらないこと
-リリースはやらない

*created by [cron-linear-issue](リポジトリのリンク)*

そして、GitHub Actions のワークフローを用意することで、以下のようなタスクが定期的に生成されるようになりました。

上で記述した Markdown によって生成された Issue
上で記述した Markdown によって生成された Issue

やってみた結果

新しい依存関係更新フローの運用を始めてから 1 ヶ月強ほど経ち、定期的に assam の依存関係更新をするようになりました。

定期的にプルリクエストが作成され、自動で作成されるタスクのおかげで更新作業を忘れない上に、動作確認手順書のおかげでメンバーに関係なく同じ品質の動作確認が行えるようになりました。その後実際にリリースする場面がありましたが、依存関係に関するトラブルは発生せず、スムーズに assam をリリースできました。

始めて数回の頃はまだプロセスに慣れていませんでしたが、だんだんメンバーも慣れてきて更新にかかる時間も減ってきました。また、週一で更新作業をやっていたところ「assam のリリース頻度から考えるに月一で依存関係を更新しても良さそう」という話になり、途中から月一で更新するように変更しました4

やってみた結果、事前に決めた要求を継続的に満たすことができ、スムーズに assam をリリースできるようになりました。

まとめ

頻繁に更新しないような OSS を長期的に運用していくためにこまめな依存関係更新はかかせません。

この記事では継続的に細かく依存関係を更新するために、生産性向上チームが行った取り組みを紹介しました。この取り組みによって、チームが抱えていた「依存関係の更新を細かく行えていない」という問題を解決し、assam のリリースがスムーズに行えるようになりました🎉

OSS である assam に対して今回の仕組みを適用しましたが、社内で生産性向上チームが開発・運用しているサービスやツールにも同じような仕組みを流用できそうだなと考えました。リリース後もしっかりと面倒を見ていきたいですね。

We're hiring!

生産性向上チームは社内エンジニアの生産性を爆上げするための活動をしているチームです 💪💪

生産性を上げることが好きな方をお待ちしております!

blog.cybozu.io


  1. 詳しくは GitHub リポジトリと「AWS + Azure ADによるSingle Sign-Onと複数AWSアカウント切り替えのしくみ作り」をご覧ください。

  2. 設定について詳しく知りたい方は Renovate 設定ファイルのドキュメント(https://docs.renovatebot.com/)を参照してください。

  3. 余談ですが、パブリックリポジトリの場合、60 日間リポジトリにアクティビティがないとスケジュールされたワークフローが無効化されるので注意が必要です。Warning: To prevent unnecessary workflow runs, scheduled workflows may be disabled automatically. When a public repository is forked, scheduled workflows are disabled by default. In a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days.https://docs.github.com/en/actions/managing-workflow-runs/disabling-and-enabling-a-workflow

  4. スパンが長くなり更新および動作確認作業を忘れる懸念がありますが、詳細な手順書があることやタスクから手順書への導線があることから、たとえ忘れても更新はできると考えます。

MOCO - Kubernetes 用 MySQL クラスタ運用ソフトウェア

$
0
0

サイボウズの Kubernetes 基盤を開発している Necoプロジェクトの ymmtです。 サイボウズ製品のほとんどはデータベースとして MySQL を採用しています。 現在 400 を越える MySQL のインスタンスを運用しており、これら全てを新しい Kubernetes 基盤に移行していく予定です。

Kubernetes 上でアプリケーションやミドルウェアの運用を自動化するソフトウェアのことをオペレーターと言います。 大量の MySQL インスタンスを Kubernetes 基盤に移行するにはオペレーターが必須であると考え、技術顧問の @yoku0825さんの監修の下で MOCOというソフトウェアを開発しオープンソースライセンスで公開しました。

本記事では Kubernetes 上の MySQL オペレーターの状況と、開発した MOCO の機能を詳細に解説いたします。

f:id:cybozuinsideout:20210531121803p:plain:h240

MySQL オペレーターの状況

私たちは以下の MySQL オペレーターソフトウェアについて採用できるか調査しました。

こちらに示す状況は 2021 年 6 月時点の情報です。

開発状況

Oracle のオペレーターは 2019 年から 2 年間開発が停滞している状況でしたが、つい先日新規に作り直すアナウンスがありました。まだプレビュー段階のようです。

Presslabs のオペレーターは活発に開発されているようですが、MySQL 8 対応版がまだリリースされておらず、またプロダクション利用はしないように明記されています。

Percona のオペレーターは活発に開発され、プロダクション利用可能なようです。

対応製品の違い

Oracle のオペレーターは MySQL (InnoDB Cluster) 専用となっています。

Presslabs と Percona のオペレーターは MySQL 互換製品である Percona Server for MySQL 専用となっています。

レプリケーション方式の違い

MySQL および互換製品の Percona Server for MySQL は複数のレプリケーション方式をサポートしています。

  1. Asynchronous Replication
  2. Semi-synchronous Replication
  3. Group Replication (InnoDB Cluster)
  4. Galera Cluster / XtraDB Cluster

詳しい解説はしませんが、レプリケーション方式により様々な制限事項や障害耐性の違いが生じます。

Asynchronous Replication ではデータベースサーバーの故障時に、過去に成功したトランザクションが消失する可能性があります。

Semi-synchronous Replication では適切に設定すると一台のサーバーが故障してもトランザクションが消失しないようにすることができます。 一方で、故障サーバーが復帰すると errant transactionという不整合データが発生する可能性があります。

Group Replication は Paxosをベースにした分散合意方式により errant transactionの発生を防止します。

Galera Cluster およびそれを元にした XtraDB Cluster は詳細に調査していません。 早々にサイボウズ製品が要求する仕様を満たせないことが判明したためです。

Oracle のオペレーターは Group Replication を採用しています。

Presslabs のオペレーターは現在 Asynchronous Replication のみのようです。Semi-synchronous にも対応を進めている様子は伺えます。

Percona のオペレーターは Galera Cluster を元にした XtraDB Cluster を採用しています。

なぜ MOCO を開発したのか

サイボウズの製品は長年レプリケーションをしない単独の MySQL サーバーを前提として開発してきました。 そのため制限事項が多いレプリケーション方式には移行することが困難な状況です。

具体的には以下の条件を満たす方式が製品開発サイドより求められました。

  1. MySQL 8 に対応していること
  2. フェイルオーバー時にトランザクションを失わないこと
  3. 2 GiB を越える大きさのトランザクションを実行できること
  4. innodb_autoinc_lock_modeに 1 を設定できること
  5. SERIALIZABLE トランザクション分離レベルをサポートしていること

結論から言いますと、これらを満たすオペレーターは存在しませんでした。

Oracle のオペレーターが採用する InnoDB Cluster は制限事項が多く、なかでも 2 GiB 以上のトランザクションを扱えない点が問題になりました。

Presslabs のオペレーターは MySQL 8 と semi-synchronous レプリケーションに未対応です。

Percona のオペレーターは 2 GiB を越えるトランザクションが扱えず、innodb_autoinc_lock_modeを 1 にできず、また SERIALIZABLE の対応は experimental でした。

上記要件を満たすものは、自社で開発を行う以外に方法がありませんでした。

MOCO について

MOCO は 1, 3, もしくは 5 台のインスタンスからなるクラスタを幾つでも作成・管理することができます。 クラスタの中の 1 台だけは書き込みが可能で、プライマリと呼びます。 他の読み込みしかできないインスタンスはレプリカと呼びます。

インスタンス間では GTIDを用いた loss-less semi-synchronous replication という方式でデータをリアルタイムで複製しています。

この方式は適切に設定することで、書き込んでいるインスタンスが落ちても他のレプリカにこれまでに成功したトランザクションが全てあることを保証できます。一方で落ちたインスタンスが復帰してくると、他のレプリカに存在しないトランザクション(errant transaction)を持ってしまう可能性があります。

特徴

MOCO は多彩かつ高度な機能を提供しています。ここではまず全機能を箇条書きで説明します。

  • MySQL 8 に対応
  • 1 台のみに書き込みが可能なプライマリ + レプリカ方式を採用
  • CLONEプラグインを利用した高速なレプリカの(再)作成
  • プライマリ用、レプリカ用の Service (Kubernetes のロードバランサ)を提供
  • データの反映が遅れているレプリカを Service から自動的に排除
  • Loss-less semi-synchronization でプライマリ障害時にデータ損失が発生しない
  • Errant transaction を持つインスタンスを自動検出しクラスタから隔離
  • 既存の MySQL から非同期レプリケーションするクラスタを作成可能
  • SERIALIZABLE トランザクション分離レベルをサポート
  • 2 GiB 以上の大きさのトランザクションも扱える
  • innodb_autoinc_lock_modeを 1 に設定できる
  • プライマリの故障を自動検出し、他のレプリカにプライマリを切り替えるフェイルオーバー機能
  • プライマリを手動で切り替えるスイッチオーバー機能
  • プライマリの Pod が削除される際に自動でスイッチオーバーする機能
  • MySQL shell を利用した高速なバックアップ
  • 定期的な自動バックアップおよび手動の臨時バックアップが可能
  • 任意の時点のデータをリストアできる Point-in-Time Recovery (PiTR)
  • クラスタ毎に異なる MySQL のバージョンを利用可能
  • 自動的で安全な MySQL のバージョンアップが可能
  • クラスタ内のインスタンスを作成後に増設可能
  • mysqld_exporterが組み込まれており、多彩なメトリクスを取得可能
  • my.cnfの設定をクラスタ毎に変更可能
  • Pod 定義をカスタマイズ可能
  • Service 定義をカスタマイズ可能
  • Slow query logをサイドカーコンテナの標準出力に転送
  • PodDisruptionBudgetを自動設定

あと、1,000 以上のクラスタを管理できるようなスケーラブルな内部アーキテクチャになっています。

使い方

MOCO のインストールは簡単です。ユーザーマニュアルに書いてあるのが正式な手順ですが、端的には cert-managerを入れて MOCO のマニフェストを apply するだけです。

$ curl -fsL https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml | kubectl apply -f -
$ curl -fsL https://github.com/cybozu-go/moco/releases/latest/download/moco.yaml | kubectl apply -f -

MySQL クラスタを作るには以下のように MySQLClusterリソースを作成します。 各フィールドの詳しい説明はリンク先のドキュメントを参照してください。

apiVersion: moco.cybozu.com/v1beta1
kind: MySQLCluster
metadata:namespace: default
  name: test
spec: # クラスタ内の mysqld インスタンスの数。1, 3, 5 から選択できます。後から増やすこともできます。replicas:3podTemplate:spec:containers: # mysqld という名前のコンテナを定義してください。必要に応じて他にコンテナや Pod の設定を入れることもできます。- name: mysqld
        # MOCO 用に補助ツールが入ったコンテナイメージを使います # 利用できるタグは https://quay.io/repository/cybozu/moco-mysql?tag=latest&tab=tags を見てください。 # 自分でビルドする方法は https://cybozu-go.github.io/moco/custom-mysqld.html を見てください。image: quay.io/cybozu/moco-mysql:8.0.25

      # 上記イメージは UID:GID 10000 で実行されます。 # もし下記 volumeClaimTemplates が作るファイルシステムが root 以外書けない場合、以下の設定が必要です。securityContext:fsGroup:10000volumeClaimTemplates: # mysql-data という名前の volume claim template を定義してください。- metadata:name: mysql-data
    spec:accessModes:["ReadWriteOnce"]resources:requests:storage: 100Gi

クラスタが構築される様子は kubectl get mysqlclusterで確認できます。 以下のように HEALTHY が True になれば準備完了です。

$ kubectl get -w mysqlcluster test
NAME   AVAILABLE   HEALTHY   PRIMARY   SYNCED REPLICAS   ERRANT REPLICAS   LAST BACKUP
test                         0                                             <no value>
test   False       False     0                                             <no value>
test   False       False     0                                             <no value>
test   False       False     0         1                                   <no value>
test   True        False     0         2                                   <no value>
test   True        True      0         3                                   <no value>

できた MySQL にアクセスするには kubectl-mocoという kubectlのプラグインを使います。 プラグインは https://github.com/cybozu-go/moco/releases/から Windows 用、Linux 用、Mac 用がダウンロードできます。

以下のようにダウンロードして PATH の通っているディレクトリに kubectl-moco (Windows は kubectl-moco.exe) という名前で配置してください。

$ mkdir -p $HOME/go/bin
$ PATH=$HOME/go/bin:$PATH
$ curl -fsL -o $HOME/go/bin/kubectl-moco https://github.com/cybozu-go/moco/releases/latest/download/kubectl-moco-linux-amd64
$ chmod a+x $HOME/go/bin/kubectl-moco

kubectl-mocoには mysql, credential, switchoverというサブコマンドがあります。 詳しい仕様はマニュアルを参照してください。

以下は MySQL データベースに書き込み可能ユーザーでプライマリインスタンスにアクセスしてデータベース・テーブルを作成し、データを入れる例です。 mysqlコマンドを対話的に実行できます。autocommit=0に初期設定されているので COMMITを明示的にしています。

$ kubectl moco -n default mysql -it -u moco-writable test

mysql> CREATE DATABASE foo;
Query OK, 1 row affected (0.00 sec)

mysql> USE foo;
Database changed
mysql> CREATE TABLE t (i INT PRIMARY KEY AUTO_INCREMENT, data TEXT NOT NULL);
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO t (data) VALUES ('aaa'), ('bbb');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)

mysql> quit
Bye

以下は読み込み専用ユーザーでレプリカ 1 番にアクセスして先ほどのテーブルの内容を確認する例です。

$ kubectl moco -n default mysql --index 1 test -- -D foo -t -e 'SELECT * FROM t'
+---+------+
| i | data |
+---+------+
| 1 | aaa  |
| 2 | bbb  |
+---+------+

しっかりレプリケーションされていますね。

MOCO は初期設定で moco-readonly, moco-writable, moco-adminというユーザーを作っています。 アプリケーションから利用する場合、これらのユーザーではなく別途ユーザーを作成したほうが良いでしょう。

$ kubectl moco -n default mysql -it -u moco-writable test

mysql> CREATE USER 'foo'@'%' IDENTIFIED BY 'xxx';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT ALL ON foo.* TO 'foo'@'%';
Query OK, 0 rows affected (0.00 sec)

Kubernetes クラスタ内の他の Pod から MySQL を利用するには Service を使います。 以下のように、MOCO は MySQLCluster 一つにつき 3 種類の Service を作成します。

$ kubectl get svc -lapp.kubernetes.io/created-by=moco
NAME                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)              AGE
moco-test           ClusterIP   None            <none>        3306/TCP,33060/TCP   40m
moco-test-primary   ClusterIP   10.96.102.109   <none>        3306/TCP,33060/TCP   40m
moco-test-replica   ClusterIP   10.96.59.78     <none>        3306/TCP,33060/TCP   40m

プライマリへの書き込みには *-primaryの Service を使います。もちろん読み込みも可能です。 ポート番号 3306 はいわゆる旧来の MySQL プロトコル、33060 は X プロトコル用です。

レプリカからの読み込みには *-replicaの Service を使います。 こちらは更新が遅延していないレプリカインスタンスに負荷分散されるようになっています。

内部構成

以下の図は MySQLCluster にたいして MOCO のコントローラー(moco-controller) が行う処理を示しています。

MySQL クラスタの内部構成
MySQL クラスタの内部構成

クラスタを作る中心となるのは StatefulSet です。 spec.replicasで指定した数の Pod が StatefulSet から作成されます。

Pod には mysqldコンテナ以外に以下のサイドカーコンテナが付随します。

  • moco-agent: CLONE 操作やレプリケーション遅延のチェックをする
  • fluent-bit: Slow query log を吸い出して標準出力に転送する
  • mysqld_exporter: mysqldの各種メトリクスを Prometheus 形式で出力する

moco-controllermoco-agentは gRPC で通信します。 この gRPC 通信の保護には mTLS (mutual TLS authentication)が用いられており、そのために moco-controllercert-managerを利用して証明書を発行し、MySQLCluster の namespace に転記しています。

また、moco-admin等のユーザーのパスワードは moco-controllerがランダムに生成して Secret に保存されています。

機能紹介

特徴の項で示した機能について解説していきます。

MySQL 8 に対応 (5.7 以前はサポートしない)

サイボウズは全ての MySQL インスタンスを 8 に更新済みであるため、MOCO は MySQL 8 を前提に設計しました。 MySQL 5.7 以前への対応を切ることで、MySQL 8 から導入された以下のメリットを享受できます。

  • Atomic DDL

    MySQL 8 からは CREATE TABLE等の DDL が InnoDB に保存されるようになり、不意の障害で中途半端な状態になることがなくなりました。

  • mysqldアップグレード処理の簡素化

    正確には MySQL 8.0.16 からですが、従前 mysql_upgradeというコマンドを実行しないといけなかった処理が不要になりました。

  • LOCK INSTANCE FOR BACKUP

    読んで字のごとくですが、バックアップ中に DDL をブロックしつつ DML は許可する新しいタイプのロックです。 プライマリからバックアップを取得しないといけないような場合にアプリケーションへの影響を最小に留めることができます。

接続の自動切換えと負荷分散

プライマリ・レプリカ方式を採用しているため、書き込めるインスタンスはクラスタ中 1 台だけとなります。 もしプライマリが変わるたびに利用しているアプリケーションが接続先を変更しなければいけないとなると、大変面倒です。

この問題を解決するため MySQL Routerという専用のソフトウェアがあるのですが、Kubernetes には宛先 Pod を動的に切り替えることができる Serviceという仕組みがあります。 Service を使うほうが構成コンポーネントを減らして簡略にできるため、MOCO は Service 方式を採用しています。

  • moco-<クラスタ名>-primary: 現在のプライマリインスタンスに繋がる Service
  • moco-<クラスタ名>-replica: データの反映が遅れてないレプリカインスタンスに負荷分散する Service

データの遅延をどの程度許容するかは MySQLCluster の spec.maxDelaySecondsで調整可能です。

apiVersion: moco.cybozu.com/v1beta1
kind: MySQLCluster
metadata:namespace: default
  name: test
spec:maxDelaySeconds:180
  ...

自動フェイルオーバー

フェイルオーバーとは、プライマリインスタンスに障害が発生したときにレプリカの一台を新たなプライマリとして設定変更することです。

moco-controller はプライマリの以下の障害を検出し、自動的にフェイルオーバー操作を実行します。

  • 接続不能
  • データ消失

特徴としては、接続不能時に Node を fencingしなくてもフェイルオーバーを実行できることです。

一般的に、観測者のネットワークが分断されている状況であるサーバーに接続不能となっていてもアプリケーションからは当該サーバーが継続的に利用可能である可能性があります。

この状況で不用意に他のサーバーをプライマリに昇格してしまうと、アプリケーションからは二つ書き込み可能なサーバーが出現してしまい、それぞれに異なるデータを書き込んでしまう恐れ(スプリットブレイン)が発生します。 スプリットブレインを防止するために良く使われる技法が fencingで、当該サーバーの電源を別の手段で落とすといったことが行われます。

MOCO は MySQL の semi-synchronous replication の仕様をうまく利用することで fencing しなくても安全にプライマリを切り替えています。 詳細は省きますが、詳しく知りたい方は設計文書や実装を調べてみてください。

手動および自動のスイッチオーバー

スイッチオーバーとは、正常に稼働しているプライマリインスタンスを速やかにレプリカの一台と役割を入れ替えることです。

MOCO は kubectl moco switchoverという kubectl のプラグインコマンドを提供しているので、スイッチオーバーを手動で行うことが可能です。 以下の例では PRIMARY が 0 から 1 に変わっています。

$ kubectl get mysqlclusters test
NAME   AVAILABLE   HEALTHY   PRIMARY   SYNCED REPLICAS   ERRANT REPLICAS   LAST BACKUP
test   True        True      0         3                                   2021-05-31T19:37:58Z

$ kubectl moco switchover test

$ kubectl get mysqlclusters test
NAME   AVAILABLE   HEALTHY   PRIMARY   SYNCED REPLICAS   ERRANT REPLICAS   LAST BACKUP
test   True        True      1         3                                   2021-05-31T19:37:58Z

さらに、MOCO はプライマリインスタンスの Pod の metadata.deletionTimestampがセットされると自動でスイッチオーバーを開始します。

どのような状況で発生するかというと、例えば MySQLCluster の Pod テンプレートをちょっと修正するといった場面です。 この場合全ての mysqld Pod が順番に削除されて作り直されるので、自動でスイッチオーバー処理を行うことでダウンタイムを極小化するわけです。

実際のダウンタイムがどの程度になるかは、ぜひ検証いただければ嬉しいです!

Errant transaction を持つインスタンスを自動検出しクラスタから隔離

Errant transactionは簡単に言えばレプリカに存在するがプライマリインスタンスには存在しないトランザクションのことです。 本来読み込み専用であるレプリカにこのようなトランザクションが入り込むはずはないのですが、不具合や人為的な操作で紛れ込む可能性はあります。

それ以外に、MySQL の semi-synchronous replication ではプライマリインスタンスが障害を起こし、フェイルオーバーしたあとにレプリカとして復帰すると、新しいプライマリが持っていないトランザクションをクラッシュリカバリー操作の結果持つ可能性があります。

このため MySQL のマニュアルでは以下のように障害を起こしたインスタンスは破棄するように注意しています。

MySQL 公式マニュアルの注意書き
MySQL 公式マニュアルの注意書き

MOCO は安全性を考慮してインスタンスは破棄しません。別のいいかたをするとデータの削除やその後の初期化はしません。 その代わりにプライマリインスタンスにないトランザクションを持つインスタンスを自動的に検出し、クラスタから排除します。 インスタンスの破棄はユーザーが手動で行う必要があります。

既存の MySQL から非同期レプリケーションするクラスタを作成可能

すでに稼働している MySQL からデータをレプリケーションするクラスタを作成できます。 レプリケーション元の MySQL は MOCO で構築したものでも、そうでないものでも大丈夫です。

この機能を利用すると、例えば地理的に離れたデータセンター間で災害対策のためにレプリケーションするといったことが可能です。

ただし Clone Pluginを使って開始時にデータを複製する関係で、レプリケーション元と MOCO のクラスタの MySQL は原則として同じバージョンである必要があります。 また、GTID を有効にしておく必要があります。

詳しい手順はユーザーマニュアルを参照してください。

単体構成の MySQL との高い互換性

Group Replication / InnoDB Cluster や Galera Cluster では満たせない以下の要件を充足できます。

  • SERIALIZABLE トランザクション分離レベルをサポート
  • 2 GiB 以上の大きさのトランザクションを扱える
  • innodb_autoinc_lock_modeを 1 に設定できる

バックアップとリストア

MOCO のクラスタは定期的にバックアップするように設定が可能です。

以下のように BackupPolicyというリソースを作成します。 バックアップデータは Amazon S3 互換のオブジェクトストレージに保存するため、権限を持つ ServiceAccount を指定したり環境変数で必要なクレデンシャルを渡せるようになっています。

apiVersion: moco.cybozu.com/v1beta1
kind: BackupPolicy
metadata:namespace: default
  name: daily
spec: # CRON 形式でスケジュールが指定可能ですschedule:"@daily"jobConfig: # S3 にアクセスするための権限を持つ ServiceAccount を指定できます # お使いのオブジェクトストレージがそういう形では認証できない場合、"default" で良いですserviceAccountName: default

    # 例えば MinIO なら環境変数でクレデンシャルを渡せますenv:- name: AWS_ACCESS_KEY_ID
      value: minioadmin
    - name: AWS_SECRET_ACCESS_KEY
      value: minioadmin

    # バックアップを保存する bucket の情報です。bucketName 以外はオプションです。bucketConfig:bucketName: moco
      region: us-east-1
      endpointURL: http://minio.default.svc:9000
      usePathStyle:true # 作業ディレクトリ用の volume を指定してください。データが大きいと見込まれる場合 # emptyDir より generic ephemeral volume 等が適切ですworkVolume:emptyDir:{}

作成した BackupPolicy を MySQLCluster から以下のように参照します。

apiVersion: moco.cybozu.com/v1beta1
kind: MySQLCluster
metadata:namespace: default
  name: test
spec: # 同じ Namespace に存在する BackupPolicy の名前backupPolicyName: daily
  ...

そうすると、以下のような CronJob が自動的に作成されます。

$ kubectl get cronjobs
NAME               SCHEDULE   SUSPEND   ACTIVE   LAST SCHEDULE   AGE
moco-backup-test   @daily     False     0        <none>          11s

臨時にバックアップを取るには kubectl create jobを使います。 空のインスタンスだとバックアップがエラーになるので、何かデータを入れてから実施してください。

$ kubectl create job --from=cronjob/moco-backup-test backup-now
job.batch/backup-now created

$ kubectl get jobs backup-now
NAME         COMPLETIONS   DURATION   AGE
backup-now   1/1           2s         5s

成功したバックアップの情報は MySQLCluster の status.backupに記録されます。 Prometheus のメトリクスとしても出力されます。

$ kubectl get mysqlclusters test
NAME   AVAILABLE   HEALTHY   PRIMARY   SYNCED REPLICAS   ERRANT REPLICAS   LAST BACKUP
test   True        True      0         3                                   2021-05-31T19:20:24Z

$ kubectl get mysqlclusters test -o json | jq .status.backup
{
  "binlogFilename": "binlog.000001",
  "binlogSize": 0,
  "dumpSize": 20480,
  "elapsed": "144.786401ms",
  "gtidSet": "d7f0a656-c243-11eb-8f30-a2a44bcdb3f8:1-3",
  "sourceIndex": 1,
  "sourceUUID": "d6b23081-c243-11eb-8609-aecb58bb22b1",
  "time": "2021-05-31T19:20:24Z",
  "warnings": null,
  "workDirUsage": 7886
}

バックアップは MySQL ShellInstance Dump Utilityを使用しています。 この方式は LOCK INSTANCE FOR BACKUPを利用しているためバックアップ中でも DML が実行できます。 また mysqldump, mysqlpumpと比較して非常に高速です。

cf. MySQL Shell Dump & Load part 2: Benchmarks

初回バックアップではフルダンプを取るだけで binary log はまだ保存しません。 二回目以降のバックアップで、Point-in-Time Recovery のために binary log を差分保存します。

$ kubectl create job --from=cronjob/moco-backup-test backup-now2
job.batch/backup-now2 created

$ kubectl get jobs backup-now2
NAME          COMPLETIONS   DURATION   AGE
backup-now2   1/1           2s         2s

$ kubectl get mysqlclusters test -o json | jq .status.backup
{
  "binlogFilename": "binlog.000001",
  "binlogSize": 662,
  "dumpSize": 20480,
  "elapsed": "197.541044ms",
  "gtidSet": "d7f0a656-c243-11eb-8f30-a2a44bcdb3f8:1-5",
  "sourceIndex": 1,
  "sourceUUID": "d6b23081-c243-11eb-8609-aecb58bb22b1",
  "time": "2021-05-31T19:37:58Z",
  "warnings": null,
  "workDirUsage": 7879
}

リストアするには以下のように spec.restoreを指定した MySQLCluster を作成します。 restorePointにはリストアしたいデータの日時を RFC3339形式で指定します。

apiVersion: moco.cybozu.com/v1beta1
kind: MySQLCluster
metadata:namespace: default
  name: restore
spec:restore: # リストアしたい MySQLCluster の namespace と name を指定してください # MySQLCluster リソース自体は失われていても問題ありませんsourceNamespace: default
    sourceName: test

    # リストアしたいデータの日時を RFC3339 形式で指定してくださいrestorePoint:"2021-05-31T19:37:20Z" # BackupPolicy の jobConfig と同様jobConfig:serviceAccountName: default
      env:- name: AWS_ACCESS_KEY_ID
        value: minioadmin
      - name: AWS_SECRET_ACCESS_KEY
        value: minioadmin
      bucketConfig:bucketName: moco
        region: us-east-1
        endpointURL: http://minio.default.svc:9000
        usePathStyle:trueworkVolume:emptyDir:{}
  ...

作成すると、moco-restore-<クラスタ名>という Job が自動的に作成されてリストアが開始されます。 この Job はリストア完了後はいつ削除しても大丈夫です。

$ kubectl get jobs moco-restore-restore
NAME                   COMPLETIONS   DURATION   AGE
moco-restore-restore   1/1           14s        25s

$ kubectl get mysqlclusters restore
NAME      AVAILABLE   HEALTHY   PRIMARY   SYNCED REPLICAS   ERRANT REPLICAS   LAST BACKUP
restore   True        True      0         1                                   <no value>

$ kubectl moco mysql restore -- -D foo -t -e 'SELECT * FROM t'
+---+------+
| i | data |
+---+------+
| 1 | aaa  |
| 2 | bbb  |
+---+------+

リストア処理の詳細は Job が作った Pod のログで確認できます。 Job を削除する前にログの内容を確認しておきましょう。

$ kubectl logs moco-restore-restore-vj2ql
{"level":"info","ts":1622490569.9764667,"msg":"waiting for a pod to become ready","name":"moco-restore-0"}
{"level":"info","ts":1622490571.98407,"msg":"waiting for the mysqld to become ready","name":"moco-restore-0"}
{"level":"info","ts":1622490581.0054455,"msg":"restoring from a backup","dump":"moco/default/test/20210531-192024/dump.tar","binlog":"moco/default/test/20210531-192024/binlog.tar.zst"}
Loading DDL, Data and Users from '/work/dump' using 4 threads.
Opening dump...
Target is MySQL 8.0.25. Dump was produced from MySQL 8.0.25
Checking for pre-existing objects...
Executing common preamble SQL
Executing DDL script for schema `foo`
[Worker000] Executing DDL script for `foo`.`t`
Analyzing table `foo`.`t`
[Worker001] foo@t@@0.tsv.zst: Records: 1  Deleted: 0  Skipped: 0  Warnings: 0
Executing user accounts SQL...
Executing common postamble SQL
Resetting GTID_PURGED to dumped gtid set
1 chunks (1 rows, 6 bytes) for 1 tables in 1 schemas were loaded in 1 sec (avg throughput 6.00 B/s)
0 warnings were reported during the load.
{"level":"info","ts":1622490581.1993299,"msg":"loaded dump successfully"}
{"level":"info","ts":1622490581.231039,"msg":"applied binlog successfully"}
{"level":"info","ts":1622490581.2483056,"msg":"restoration finished successfully"}

バージョンアップ

MySQL のバージョンを上げるには、MySQLCluster の mysqldコンテナのイメージを差し替えるだけです。 必要な処理は全て MOCO が自動的に行います。念のため、バージョンアップ前に臨時バックアップを取るのがお勧めです。

なお、ダウングレーディングは MySQL がサポートしていないため MOCO もサポートしていません。

メトリクス

MOCO は各 MySQLCluster および mysqldのインスタンスについて多種のメトリクスを Prometheus 形式で出力しています。

InnoDB の統計情報などは mysqld_exporterで取得できる機能が備わっています。 以下のように spec.collectorsを MySQLCluter に指定すると mysqld_exporterがサイドカーコンテナとして追加され、InnoDB や performance_schema の統計情報を出力可能です。

apiVersion: moco.cybozu.com/v1beta1
kind: MySQLCluster
metadata:namespace: default
  name: test
spec: # mysqld_exporter の collector flag (collect. は省略) を指定してください # 詳しくは https://github.com/prometheus/mysqld_exporter/blob/master/README.md#collector-flagscollectors:- engine_innodb_status
  - info_schema.innodb_metrics
  ...

出力されるメトリクスの詳細や scraping ルールについてはユーザーマニュアルをご覧ください。

my.cnfのカスタマイズ

my.cnfの設定は ConfigMap を作成することで行えます。 以下のように dataの key-value 形式で指定してください。

apiVersion: v1
kind: ConfigMap
metadata:namespace: default
  name: mycnf
data:long_query_time:"0"innodb_log_file_size:"10M"

作成した ConfigMap 名を以下のように MySQLCluster で指定すれば設定が変更できます。

apiVersion: moco.cybozu.com/v1beta1
kind: MySQLCluster
metadata:namespace: default
  name: test
spec: # 同一 Namespace 内の ConfigMap 名mysqlConfigMapName: mycnf
  ...

performance-schema-instrument等一部の設定は複数回指定しないといけないため、ConfigMap の key-value 形式では指定できません。

このような場合 _includeというキーに my.cnfにそのまま転記される内容を書くことができます。

apiVersion: v1
kind: ConfigMap
metadata:namespace: default
  name: mycnf
data:_include: |
    performance-schema-instrument='memory/%=ON'
    performance-schema-instrument='wait/synch/%/innodb/%=ON'
    performance-schema-instrument='wait/lock/table/sql/handler=OFF'
    performance-schema-instrument='wait/lock/metadata/sql/mdl=OFF'

innodb_buffer_pool_sizeの自動設定

innodb_buffer_pool_sizeは MySQL の性能に極めて重要な影響を持つパラメーターです。

ConfigMap で指定した my.cnfinnodb_buffer_pool_sizeが指定されておらず、mysqldコンテナの resources.requests.memory (ない場合は resources.limits.memory) が指定されている場合、MOCO はこのパラメータを自動設定します。

例えば以下の MySQLCluster では 100 GiB のメモリをリクエストしているので、innodb_buffer_pool_size70Giに設定されます。

apiVersion: moco.cybozu.com/v1beta1
kind: MySQLCluster
metadata:namespace: default
  name: test
spec:podTemplate:spec:containers:- name: mysqld
        image: quay.io/cybozu/moco-mysql:8.0.25
        resources:requests:memory: 100Gi

Pod 定義のカスタマイズ

これまで見てきたように、MySQL Pod の定義は自在にカスタマイズできます。

お勧めは podAntiAffinityで Pod が複数のホストに分散するように設定することです。

apiVersion: moco.cybozu.com/v1beta1
kind: MySQLCluster
metadata:namespace: default
  name: test
spec:podTemplate:spec:affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: app.kubernetes.io/name
                operator: In
                values:- mysql
              - key: app.kubernetes.io/instance
                operator: In
                values:- test   # metadata.name の値を指定topologyKey:"kubernetes.io/hostname"
  ...

app.kubernetes.io/nameapp.kubernetes.io/instanceは MOCO が自動的につけるラベルです。

Service 定義のカスタマイズ

プライマリとレプリカ用に作成される Service もカスタマイズできます。

例えば type: LoadBalancerにすれば、Kubernetes クラスタの外部からアクセス可能な MySQL になります。

apiVersion: moco.cybozu.com/v1beta1
kind: MySQLCluster
metadata:namespace: default
  name: test
spec:serviceTemplate:spec:type: LoadBalancer
  ...

Slow query log

MySQL は実行に長時間かかったクエリを記録する slow query logという機能があります。 運用上不可欠に近いものです。

Slow query log は mysqldの標準出力ないし標準エラー出力には出せないため、MOCO slow log の内容を吸い出して標準出力に流すサイドカーコンテナを自動で追加しています。 また、slow query log のファイルは一定時間毎に自動でローテートされています。

$ kubectl logs moco-test-0 -c slow-log
/usr/local/mysql/bin/mysqld, Version: 8.0.25 (Source distribution). started with:
Tcp port: 3306  Unix socket: /run/mysqld.sock
Time                 Id Command    Argument

Loki等でコンテナのログを収集している環境であれば、slow query log の内容も自動で収集することが可能です。

PodDisruptionBudget を自動設定

PodDisruptionBudgetとは Kubernetes の Node 退役処理などで Pod を drain する際、アプリケーションが壊れないように制限を課す機能です。

MOCO はインスタンス数が 3 の MySQLCluster であれば 1 つまで、インスタンス数が 5 の MySQLCluster であれば 2 つまで Pod が drain されて良いような PodDisruptionBudget を自動で作成します。

まとめと今後の予定

サイボウズでは今後 cybozu.com の全ての MySQL インスタンスを自社 Kubernetes 基盤である Neco に移行していきます。 MOCO はその作業の要となるソフトウェアで、近い将来数百のクラスタを稼働することになる見通しです。

MOCO は現時点で最も機能が充実している MySQL オペレーターの一つです。ぜひお試しください。

github.com

Neco プロジェクト完了したので人材募集します!

$
0
0

Neco プロジェクト責任者の ymmtです。

Neco プロジェクトは、2018年に開始したサイボウズのクラウドサービス基盤を刷新するプロジェクトです。 三年半の期間を経て、この度プロジェクトの目標をすべて達成できました。

そこで今回は社内で完了報告をした資料を抜粋して、改めて Neco プロジェクトの概要と成果についてお伝えします。

また、プロジェクトは完了しましたが、作り上げた新基盤「Neco」の活用はまだまだこれからの段階です。 基盤は使ってなんぼなのでこれからが仕事としては大変かつ面白いことになります。

そのようなわけで、クラウドサービス基盤の開発・運用に携わりたい方をしています! 今後の計画についても触れていますので、ぜひご一読いただければ幸いです。

f:id:cybozuinsideout:20191105145211p:plain:w240

プロジェクトのまとめ

Neco プロジェクト概要

サイボウズのクラウドサービス cybozu.comは 2011 年にサービス提供を開始しました。 国内データセンターで構築した独自基盤で稼働していますが、アーキテクチャが古く様々な問題を抱えるに至ったため、2018 年に抜本的な改善を開始しました。それが Neco プロジェクトです。

主な目標として、以下の三項目を掲げました。

  • 数千台のサーバーの容易な管理
  • スケーラビリティの大幅な向上
  • サービスのダウンタイムの大幅な削減

目標達成状況

まずサーバーの容易な管理については、ネットブートとコンテナ実行に最適化された OS を採用することで更新作業を大幅に簡略化しました。 また、Kubernetes クラスタに組み込むサーバーについても自動で選択・入れ替えできる機能を開発しました。

スケーラビリティ面では、ストレージデバイスは原則として NVMe SSD にすることで I/O 性能を大幅に向上しました。 また、分散オブジェクトストレージ Cephを導入してサーバー台数を増やせばストレージシステムの容量と性能を増やせるようにしました。

ダウンタイムの削減に関しては、MySQL は MOCOというソフトウェアで冗長化と自動運用を実現しました。 その他のミドルウェア群も冗長化した結果、今では毎週全サーバーを再起動してもサービスが継続できるようになっています。

成果紹介

Neco のアーキテクチャ

Neco のシステムはデータセンター全体を Kubernetes クラスタとして管理します。 実際利用者は kubectlや Argo CD といった Kubernetes 用のソフトウェアで Neco を使います。

Kubernetes クラスタを自動運用する CKEや高機能なネットワークブートサーバーである sabakanを自社開発した結果、数千台のサーバーも楽に運用できるようになっています。 例えばサーバーを退役して適切な別のサーバーを選択して入れ替える処理は、コマンド一つで実行できるようになっています。

Kubernetes を稼働したままサーバーを安全に順次再起動する仕組みやファームウェアを自動で更新する仕組みも開発しました。

マルチテナンシー

サイボウズは多数の開発チームを抱えています。 これらの開発チームが他のチームのリソースに影響を与えないように、Neco の Kubernetes は各種のマルチテナンシー機能を実装しています。

上の画像は Teleport という認証プロキシで Kubernetes にアクセスしている様子です。 上下の画像は異なる開発チームがアクセスしている様子で、利用可能なサーバーが異なっていることがわかります。

全ての開発チームが共通で利用できる仕組みも多数用意しています。 Teleport もそうですし、後述するモニタリング基盤や Argo CD などは各自が用意しなくてもすぐに使えるようになっています。

ネットワーク機能

クラウドサービスを動かす基盤であるため、ロードバランサや NAT 機能をオンデマンドに利用できるようになっています。 もちろん、TLS 証明書も自動で発行・更新できます。

監視基盤

Neco で力が入っているのが監視基盤と呼んでいる仕組みです。 運用とはすなわち監視のことである、と言いたいくらいサービスの稼働状況の監視は大事です。

そこで Neco では Loki, VictoriaMetrics operator, Grafana operator による監視基盤を用意して開発チームが簡単にサービスを監視できるようにしました。 詳しくは以下の記事をご覧ください。

blog.cybozu.io

オートスケール機能

Kubernetes の強力な機能の一つがオートスケール機能です。 簡単にいうと、負荷に応じて Pod を自動的に増減してくれる機能です。

Neco は prometheus-adapterkube-metrics-adapterを利用しているので CPU やメモリ使用量に加え、cAdvisorの提供するコンテナ単位のメトリクスや、PromQL による任意のクエリで Pod をオートスケールできます。

また、拡張可能なボリュームについてもファイルシステムの空き容量を見て自動的に拡張する pvc-autoresizerを開発し、利用者に提供しています。

MySQL (MOCO)

サイボウズでは 400 以上の MySQL のインスタンスを動作させています。 これらを Kubernetes 上でクラスタ化し、自動的にフェイルオーバーやバックアップするためのソフトウェア MOCO を開発しました。

詳しくは以下の記事をご覧ください。

blog.cybozu.io

分散オブジェクトストレージ

前述の通り、スケールアウトする分散オブジェクトストレージとして Ceph を導入しました。 正確に言うと Ceph を Kubernetes 上で自動運用する Rookを改良して導入しています。

その取り組みが認められ、Neco チームのメンバーが Rook のメンテナに就任しています

Elasticsearch / Kibana

MySQL 同様、Elasticsearch のクラスタも Kubernetes 上で自動運用しています。 こちらは Elastic 社の Elastic Cloud on Kubernetes (ECK)という製品を導入しました。

オープンソース

ここまで紹介してきたソフトウェアの大半はオープンソースです。 Neco の成果物もほぼ 100% オープンソースライセンスで公開していて、どなたでも利用可能です。

実際 github.com/cybozu-go/necogithub.com/cybozu-go/neco-appsの組み合わせはほぼ Kubernetes ディストリビューションに相当します。 どなたでも、この記事に紹介してきたような Neco クラスタを構築できるわけです。 (ただし運用するのは大変でしょう)

そのほかの成果

(picture by CloudNative Computing Foundation under CC-BY-NC 2.0)

Kubernetes および CloudNative 技術のカンファレンスである KubeCon + CloudNativeCon で弊社メンバーが計3回登壇できました。 KubeCon は 10,000 人規模の参加者が集うオープンソース系では世界最大規模のカンファレンスです。

その他にも国内で多数発表・登壇しています。

現状と今後の予定

Neco の利用状況

Neco アーキテクチャのデータセンターは現在 3 箇所で稼働しています。

各データセンターで Pod 数にして約 800 個、データ量にして数テラバイト単位のワークロードが動いていますが、Neco 基盤への移行はほんの序盤です。 データを持つワークロードの移行は今年後半から本格化していきます。

今後の体制

Neco は今後は基盤・プロダクトとして継続的に開発・発展させていきます。

具体的には以下3チーム体制になります。全チーム、絶賛人員募集中です!

  • Neco (Kubernetes) チーム

    マルチクラスタ連携、高度な L4 ロードバランサ、Serverless 基盤、CI/CD システムの改善などなど今後も取り組むべき課題がたくさんあります。

    Kubernetes 専門家として腕を磨きたい方、社内に最高のインフラを提供したい方はぜひ応募してください。

  • CSA (ストレージ) チーム

    ストレージコンポーネントの設計・開発・運用を行うチームです。 今後の大きな仕事は、旧基盤からのペタバイトクラスのデータの移行作業です。

    ソフトウェア、ハードウェアを問わず不具合調査・評価・チューニングを繰り返していく作業に興味がある方はぜひ応募してください。

  • Maneki (移行) チーム

    旧基盤からの移行作業を統括して進めるチームです。現在ElasticsearchやMySQLなどのミドルウェアの移行を進めています。 利用者向けのトレーニングも担当しており部署横断的な仕事が多くあります。

    Kubernetes 専門家である必要は必ずしもありません。業務内容にご関心がある方はぜひ応募してください。

以上、Neco プロジェクトは完了したけれど Neco の発展はこれからというお話でした。 今後も折々に情報をお伝えしていきます。

オンラインイベントで気を付けるべき10のポイント

$
0
0

こんにちは。 暑さでMacBookのファンが騒がしい季節になりました。 コネクト支援チームの@ueokandeです。

COVID-19によって世の中の勉強会・イベントはすっかりオンラインにシフトしました。 サイボウズも、Cybozu Tech Meetupを開催しており、社内の開発文化や技術情報をオンラインで発信しています。 この記事では、この1年でイベントの運営や自ら発表して気づいた、オンラインイベントで気を付ける10のポイントを紹介します。 これから登壇する方や、オンラインイベントを企画する方の役に立てれば幸いです。

その1. 社外秘の映り込みに注意しよう

画面共有で社外秘を映している図

イベントを運営する上で自分が最も気を付けているのは「社外秘の映り込み」です。 もちろんオフラインイベントなら大丈夫というわけではないですが、オンラインだと情報が拡散しやすく削除も難しいので、オフライン以上に社外秘の映り込みには気を付けています。

起こりうるアクシデントは、画面共有でうっかり社内の情報を映してしまうことです。 Cybozu Tech Meetupでは発表者に、事前にデスクトップをきれいにしてもらったり、不要なアプリケーションを終了するようお願いしています。 イベント当日も、本番開始前に流れを確認するとともに、画面共有されるものに問題が無いかチェックしています。

その2. デスクトップ通知に気を付けよう

デスクトップ通知を画面共有している図

デスクトップをキレイにすれば万全、ということはありません。 落とし穴はまだあります。 その1つがデスクトップ通知です。

SlackやDiscord、メールクライアントの、デスクトップ通知を有効にしている人も多いでしょう。 画面共有中に運悪くメッセージが届けば、うっかり社外秘情報やプライベート情報などを映してしまう恐れがあります。 これらのアプリケーションも当日利用しないのであれば、あらかじめ終了しておくのが無難です。

その3. Webブラウザの落とし穴

Webブラウザの恥ずかしい履歴を画面共有している図

WebブラウザでGoogle SlidesやGitHubなどを映すときも注意が必要です。 社内システムや、メール、社内のプロジェクトやソースコードなどを、Webブラウザで閲覧している人も少なくないんじゃないでしょうか。

Google Chromeならゲストモードに切り替えたり、Firefoxならプロファイルを切り替えるなどをして、履歴やブックマークが映らないようにするのがよいです。 あらかじめ映すページを開いておくと、登壇中の操作ミスを防ぐこともできます。

その4. 事前にOSアップデートを済ませよう

OSアップデートが通知されている図

OSアップデートはオフライン時代からのよくあるトラブルです。 不幸なことに、オンラインイベントでPCが再起動してしまうと、発表者と運営の連絡手段も絶たれてしまいます。 最近は強制再起動されることは少なくなりましたが、それでも発表中に通知が出てしまうと慌ててしまいます。 イベント当日までにソフトウェアのアップデートが蓄積されていないか、強制再起動されないか確認して、もし可能ならPC以外の連絡手段も確認しておくとよいです。

その5. スライドのレイアウト

スライドの大事な部分が隠れている図

ZoomのYouTube Liveへの配信機能は非常に便利で、特別な機材が無くてもお手軽にオンラインイベントを開催できます。 しかしZoomの配信機能は、画面右上にカメラ映像が、画面右下にZoomのロゴが表示されることを覚えておく必要があります。 可能な限り画面端には重要な情報を置かない方がよいでしょう。 もし可能であれば、事前にYouTube Liveでスライドを映してみて、どの程度スライドが隠れるかチェックするとよいです。

その6. そのバーチャル背景は大丈夫?

バーチャル背景にアニメが映っている図

Zoomのバーチャル背景機能を使う場合は、バーチャル背景が版権をクリアしているか、公序良俗に違反しないかをチェックしましょう。 初めてのオンライン登壇だと「いいバーチャル背景がない」という人もいます。 Cybozu Tech Meetupでは、発表者がバーチャル背景を探す手間を減らすために、運営側で発表者プロフィールが書かれたバーチャル背景を用意しています。

その7. 自分では気付かない文字サイズ

4Kディスプレイで小さい文字を共有している図

高解像度のディスプレイを使っている人は、文字サイズが適切かを確認しましょう。 会議などで「文字が見えないです~」と言われた人もいるのではないでしょうか。 画面共有している本人からは、文字サイズが小さいということに気付きづらいので、別の人が確認するとよいです。 発表前に適切な文字サイズで表示しておき、余裕をもって本番に挑むのがよいです。

その8. 発表環境にも気を付ける

スマートスピーカーが鳴って慌てている図

デスクトップだけでなく、登壇時の周りの環境も確認しましょう。 オンラインイベントでは自宅で登壇する人がほとんどです。 うっかりスマートスピーカーが起動したり、家の人から呼ばれたりしないかチェックしましょう。 「恥ずかしい」で済む程度のトラブルですが、発表中に呼ばれると動揺してうまく話せなくなるかもしれません。

その9. いつも使っている機材で

照明が暗くマイクがミュートになっている図

マイク・カメラは必ずしも高級な機材を準備する必要はありません。 慣れていないデバイスを使うと、トラブルシュートができずに焦ってしまうので、Cybozu Tech Meetupでは普段使っているものを用意してもらってます。 自分では気付きにくいので、運営や第三者に事前にチェックしてもらうとよいです。

その10. リハーサルをしよう

リハーサルをしている図

時間に余裕があれば、ぜひイベントのリハーサルをしてください。 やはり社内向け勉強会と少し勝手が違うので、当日の流れを参加メンバーで確認しましょう。 またリハーサルでは、当日と同じ機材を使うことも忘れないでください。

Cybozu Tech Meetupでは、当日どういう流れで画面共有をするか、マイク・カメラは問題ないかなどを、運営と発表者で確認しています。 リハーサルをすることで、初めて気づく手順の見直しが見つかることがあります。

おわりに

Cybozu Tech Meetupでチェックしている点は実は10以上ありますが、この記事では自分が特に気を付けている点を紹介しました。 Cybozu Tech Meetupの運営では、何より発表者が発表に集中できるよう心がけています。 そのために事前にできる確認や準備を済ませておき、当日のトラブルやミスを最小限にしています。 オンライン登壇にチャレンジしたい方や、これからイベントの企画をしようと思っている方は、ぜひ参考にしていただければと思います!

魔窟と化した全文検索サーバーとふっかつのじゅもん

$
0
0

サイボウズのクラウド黎明期から運用し続けていたSolrサーバーを Elasticsearchに置き換えるプロジェクトが先日完了しました。

プロジェクト完了報告もかねてプロジェクトのあらましを公開したいと思います。

はじめに

このプロジェクトの主軸は『魔窟と化したレガシー技術をどう捌くか?』になります。 このプロジェクトの報告をする前に、いくつかエクスキューズをさせていただきます。

  • クラウド黎明期を支えてくれたSolrには畏敬の念に近い感謝をもっています
  • レガシーな技術に対してマウントやディスリスペクトの意図はありません
  • 魔窟にかかわることになってしまった人に対して負の感情は一切ありません
  • 今回の採用している構成はElasticsearchのあるべきアーキテクチャではありません
    • 今後、Neco環境への移行を通して継続的に改善していきます

サイボウズでのSolrの使い方と用語説明

サイボウズのクラウドサービスでは、サービス開始当初から長年にわたり全文検索サーバーとしてSolrを採用していました。弊社では、次のようにSolrを使っています。

  • アプリケーションサーバーやデータベースなど、プロダクトを運用するために必要なコンポーネントごとにVMを配置しています。SolrサーバーもVMに配置しています
  • サイボウズのクラウドサービスはマルチテナント型のサービスとなっています。お客様(テナント)ごとに独立した環境を提供しています。1サーバーですべてのテナントを賄うのではなく、一定数のテナントごとにSolrやDBサーバーを用意しています

cybozu.comのクラウド概要図
cybozu.comのクラウド概要図

  • お客様別にデータを分離するため、プロダクトごと、テナントごとにSolrの「インデックス」を作成しています。つまり、各Solrサーバーには多数のインデックスが格納されています

ここで、インデックスとは「Solrの検索データをためる箱」のことです。Solrの用語としては「コア」と呼ぶべき物ですが、一般にインデックスと呼ばれることが多いことやElasticsearchの用語でもインデックスと呼ぶことから、この文書では一貫してインデックスと記載します。

他に、この文章では次のように用語を使います。

  • ユーザーの作成したデータをインデックスに検索データとして保存することをインデクシングと記載します
  • バッチプログラムでインデックスを再作成することを再インデクシングと記載します
  • 全文検索サービスを利用可能な製品を指してプロダクトと記載します
    • Solrを利用しているのはkintone、ガルーン、メールワイズの3プロダクトです

魔窟に化けるまでの歴史

それでは、どのように全文検索システムが魔窟と化していったのか、歴史を紐解いてみたいと思います。

魔窟に化けるまでの歴史1 Solrのバージョンアップができなくなる

Solrのバージョンアップに伴い、社内で利用していたトークナイザの互換性がなくなる問題が発生しました。

トークナイザとは、全文検索を実現するための技術要素の一つで、索引付けをするために文章データを単語単位や一定長などの断片に分割する機構です。 全文検索エンジンは元の文章を指すIDと、分割された文章の断片を紐づけて索引情報を作成しています。 そして、検索時は検索キーワードをトークナイザで分割し、分割された語句が索引情報にあるかを調べています。

このトークナイザがどのように文章を分割するかで検索の精度が変わります。 一般的に、トークナイザが更新され、賢くなることは検索精度向上につながり、嬉しいことが多いです。 しかしながら、トークナイザの互換性が失われる場合はインデックスデータの再作成が必要になり、 同じ検索キーワードでも検索結果が変わってしまうことに注意が必要です。

弊社プロダクトのkintoneでは、レコード絞り込み機能の一部に全文検索エンジンを使用しています。 そのため、トークナイザを変更すると、レコード絞り込み結果に大きく影響が出ることが分かりました。

これが致命的でした。当時はこれを解決する手段が思いつかず、一時的にバージョンアップを見送る対応としました。

魔窟に化けるまでの歴史2 データ規模の巨大化

これは喜ばしいことですが、クラウドサービスのリリースから数年でデータの規模が大きくなりすぎました。 結果、サービス開始初期に開発された再インデクシング用ツールが使い物にならなくなる問題に直面していました。

当時は全文検索の索引情報を再作成するためのツールをインフラチームが持っていました。 そのため、トークナイザの互換性は置いておくにしても、 このツールを実行すれば再インデクシングができるはずでした。

しかし実際には、データ規模が巨大になるにつれて途中でエラーが起きたり、 オペレーションミスで再インデクシングが途中で停止する事故が起きる状態になってしまいました。

ツールの設計上、再インデクシングが停止した場合は最初からやり直しが必要で、 非常に苦痛をともなうオペレーションとなっていました。 ツールを改修しようにも、各テナント・プロダクトの再インデクシングの実施状況や進捗などの状態管理を 一手に引き受ける非常に複雑なツールとなっており、改修が困難でした。

こうして、再インデクシングは触れてはいけないオペレーションになっていきました。

魔窟に化けるまでの歴史3 矢面に立ってくれていたコアメンバーの退職

そうこうしているうちに再インデクシングのオペレーションを矢面に立ってやってくれていたコアメンバーが 相次いで退職してしまいます。今振り返ると彼らの意思の強さに強く依存していました。

彼らの退職のタイミングあたりから再インデクシングが話題に上がったことは私の記憶ではありません。 彼らがこの記事を見ているかわかりませんが、矢面に立って作業してくれたことに感謝しかありません。

完全なる魔窟に化ける

こういったことが重なりSolrの長期間の塩漬けが決定的になりました。

一方、検索データは日々肥大化し、毎年1TB増加するテナントも存在しました。 大きなテナントでは2018年末の時点で1.8TBに達しました。 インデックスが肥大化するにつれて検索スピードは劣化していきます。

特にガルーンは顕著に検索スピードが劣化し検索リクエストのタイムアウトが頻発するようになります。 さまざまな調査を試みましたが、解決にいたらず時間だけが過ぎていく状態でした。

Elasticsearch置き換えプロジェクト 挫折編

時を同じくしてElasticsearchが登場し、日本国内でも流行の兆しを見せていました。 Elasticsearchの高度な検索機能やクラスタリングなど魅力的な機能は多かったため、サイボウズでも置き換え調査をしていました。 しかし、再インデクシングの問題やトークナイザの互換性問題は未解決のままでした。

当時社内で構想していたアーキテクチャではクラスタ構成のElasticsearchを多数運用するのはインフラの管理ルーツの変更や運用コスト的にきびしかったため、 巨大クラスタにすべてのテナントのインデックス入れて運用する構想でした。

しかし、調査を進めていると、多数のインデックスの扱いに難があることが判明します。

Elasticsearch の巨大クラスタを複数用意し、すべてのインデックスを運用することはインフラ側の変更が大きく、 この構成では構築・運用不可能という判断になり、調査そのものが突然停止することになります。

最後にすがったElasticsearchへの望みも蜘蛛の糸のように切れてしまったのでした。

Elasticsearch置き換えプロジェクト ビバーク編

そこに追い打ちをかけるように利用しているSolrのバージョンがEOLになることが分かりました。

看過できる状況ではなかったので、Solrの置き換えを強行するプロジェクトを起案します。 プロジェクト名をアウトドア用語で緊急的なエスケープを意味する『ビバーク』としました。

プロジェクトを成功させるための技術的な制約を明確化し、古いSolrからの脱却に集中しています。

Solrから脱却してElasticsearchを採用した理由ですが

  • 次世代のkintoneアーキテクチャ構想にてElasticsearchが採用されていたため技術スタックを合わせました
  • Solrアーカイブ形式が変わったため、弊社で独自カスタマイズしていた全文検索サーバーを改修して対応させるには時間がかかる
  • 独自カスタマイズしたサーバーをメンテナンスし続けるよりもElasticsearchを運用する方が維持コストは低そう

Elasticsearchに移行するために解決すべき要件は次の3点のみに絞りました。

  • Elasticsearch不安定化問題
  • トークナイザの互換性問題
  • インデックスの再作成問題

この問題を解決するためには一部を除いて難しい技術はほぼ使っていません。

タイトルを『ふっかつのじゅもん』としているのは全体として魔法のようにうまくいっているように見えますが 巧妙に制約を回避・リスクを許容範囲に収まるように手を打ち、今回の移行にこぎつけているためです。

このプロジェクトのビバークという名前にふさわしく、今の状況をとにかくしのぎ切るために移行計画を構想しています。 K.U.F.Uを方針としています。わりと普通にいうと工夫というやつです。

ふっかつのじゅもん1 インデックス数の制限を回避する

多数のインデックス作成時にElasticsearchが不安定化する問題は、Issueなどを調査した結果からクラスタに所属するElasticsearchノードの通信が原因で不安定化することが分かりました。 また、コミュニティのQ&Aからインデックスの作成で顕著に性能が劣化するがインデクシング、検索は問題なく行えることがわかってきました。 実際に社内で実験したところ、シンプルなクラスタの運用であれば2000程度のインデックスがあっても十分安定稼働すると確認できました。

性能検証としてシンプルなElasticsearchクラスタを構築し、レガシーSolrで1.8TBのインデックスになっていた環境と同データを持つ環境を再現して、検索検証を行いました。 次のような結論が得られました。

  • ガルーンは10倍程度の性能改善を期待できる
  • kintone、メールワイズは性能劣化が発生しない
  • レガシーSolrとElasticsearch 6との比較で40%のインデックス容量の圧縮効果を期待できる

SolrとElasticsearchは内部ではどちらもApache Luceneを使っているため、大きな性能の違いは発生しないだろうという推測をしていました。 Luceneの進化は素晴らしいものがあり、推測はいい意味で裏切られることが検証を通してわかりました。 ガルーンは全文データの構造を改善しているために大きな改善が得られています。

先述の通り、サイボウズのクラウドサービスでは一定数のテナントごとに独立したSolrサーバーを立てる運用をしています。このSolrサーバー同様にElasticsearchを多数運用する形であれば、 インデックス数の増大で不安定化する問題を回避できます。

さらに、Solrサーバーのデプロイと同じ要領でElasticsearchをデプロイできるので、 巨大クラスタを構築するのに比べインフラ側の修正も少なくなり、 短期間での基盤更新に向いていることから ビバークではこの構成を採用しました。

ふっかつのじゅもん2 トークナイザの互換性を確保する

Elasticsearchではトークナイザを指してアナライザ(Analyzer)と表記することが多いですが、このドキュメントでは便宜上このままトークナイザとします。

Elasticsearchを調査したところ、トークナイザはプラグイン機構で拡張できることが分かりました。 Elasticsearchが提供しているアジア圏の言語向けのトークナイザの icu-analysis-pluginの実装を調べたところ、数百行のシンプルなコードでした。

加えて、プラグインはElasticsearchに付属するコマンドラインツールで簡単にインストールできるようになっていました。 Elasticsearchにパッチをあてて再ビルドする作業は要求されません。

そこでSolrで廃止されたトークナイザのソースコードを移植してプラグインとして再実装することを思いつきます。 プロトタイプの作成を通して、プラグインのメンテナンスを続けてもコストは許容できると考えるようになりました。

そして、後方互換性の破壊に関する議論で時間を溶かすよりはプラグインとして移植するほうがトータルコストは安く上がるという結論にいたりました。

ふっかつのじゅもん3 インデックスの再作成問題をなんとかする

このプロジェクトでの技術的難所はインデックスの再作成です。 過去のインフラチームのツールの問題をふりかえって『カプセル化が不適切な結果、プロダクトが持つべき責務をインフラチームが過剰に背負ってしまっている』と解釈しました。

インフラチームの背負っていたテナント・プロダクト内部の細かい進捗状態管理の責務をプロダクトに移し、 詳細の内部実装をプロダクト内に隠蔽するアプローチを取りました。 次の図はプロダクトが内部的に隠蔽している状態遷移図です。インフラからは各プロダクトが公開するWeb APIを通じて 再インデクシングの開始・停止や進捗の確認ができますが、この状態遷移をインフラ側で保持・管理する必要がなくなりました。

プロダクト内部に隠蔽されている再インデクシングの状態遷移図
プロダクト内部に隠蔽されている再インデクシングの状態遷移図

結果、インフラからはどのテナントをいつ再インデクシングするかだけを制御すればよくなりました。

各プロダクトには、データを順次読み取り再インデクシングするバッチ処理や、進捗状況の管理が 増えることになります。しかし、これにより、プロダクトのデータの持ち方に合わせた効率の良い 再インデクシングがやりやすくなるなど、プロダクトが独自に再インデクシング処理を変更・改善しやすくなります。 また、各プロダクトはアプリケーションサーバが再起動することも考慮してバッチ処理を 実装するため、必然的に進捗状況はデータベースサーバに保存することになります。これを活かし、 再インデクシングが何らかの原因で停止しても、原因を解消後に停止したところからの再開が可能になりました。

この結果、数か月におよぶ再インデクシングが可能になりました。

ふっかつのじゅもん4 ストラングラーパターン

ストラングラーパターンとは?

ストラングラーパターンはレガシーになったサービスを新しいサービスに置き換えるための移行パターンです。 レガシーなサービスと新サービスを平行運用しながらレガシーなサービスへの依存を徐々に減らしていきます。

下図はストラングラーパターンを使ってSolrへの依存を減らしていく様子を示しています。

ストラングラーパターンを使った移行の様子
ストラングラーパターンを使った移行の様子

  • Fig1. kintoneがElasticsearch切り替え後にSolrへの切り替えに備えてインデックスを更新し続けています
  • Fig2. kintoneがElasticsearchに移行完了状態です
    • ガルーンがElasticsearch切り替え後にSolrへの切り替えに備えてインデックスを更新し続けています
  • Fig1、Fig2ともにメールワイズはSolrを利用し続けています

カナリアリリースに近しいものがありますが、特徴の一つとして平行運用期間は新旧両方のデータを更新し続けます。 バグや性能問題があった場合は古いサービスに切り戻して一時的に問題を回避しながら移行を進めていくことになります。

今回の移行プロジェクトでは再インデクシングを行うWeb APIの実装が完了したプロダクトから再インデクシングを開始し 再インデクシングが完了したテナントは Elasticsearchに切り替えていきました。 Elasticsearchの性能特性が理解できていないリスクを考慮して、データ量の少ないテナントから移行を実施していきました。 仕様差異が発生する検索処理の切り替えは、定期メンテナンスに合わせることで事前の告知作業などにも配慮して作業を進めています。

Elasticsearchへ切り替え後、不具合に備えてSolrへの切り替えをサポートする並行運用期間を2か月間設けました。 こうして再インデクシングを運用環境で開始し、徐々に全文検索のバックエンドを切り替えてSolrへの依存を減らす作業を続けていきました。

そしてついに移行完了

そして、2021年6月にすべてのSolrサーバーへの切り替えサポート期間が終了しElasticsearch移行が完了しました。

ちなみに、2018年末でSolrサーバーで1.8TBのインデックスがあったテナントは、最終的に4TBのインデックスとなっていました。 このテナントはElasticsearchへの再インデクシングに2か月もの時間を要しました。

移行振り返りその1 性能はどうでしたか?

ガルーンで特に性能劣化が顕著だったテナントでの検索にかかった時間のグラフです。 Elasticsearch切り替え後は検索結果が高速化されていることがわかります。

インデックス容量はレガシーなSolrと比較して40%圧縮される効果は検証通りの結果が得られました。

ガルーンの検索時間の改善グラフ
ガルーンの検索時間の改善グラフ

移行振り返りその2 トークナイザどうでしたか?

検索エンジンが切り替わっても検索仕様は維持できているため、仕様変更に気付かれることは稀でした。 「稀」としているのはプロダクトによっては移行のタイミングで細かい仕様変更や脆弱性改修などを行っているためです。 微妙に仕様変更が発生するケースに気付いたお問い合わせが数件ありました。

コミュニケーションコスト、メンテナンスコスト、リスク面への配慮など総合するとペイしているという判断です。

移行振り返りその3 クローラーAPIどうでしたか?

プロダクト・インフラチームに新しい十字架を背負わせてしまうので罪悪感はありました。 数か月におよぶ再インデクシングを乗り越えてSolrから乗り換えれたのでご容赦いただきたい...。

移行振り返りその4 ストラングラーパターンどうでしたか?

ストラングラーパターンが特に役に立った例として、Elasticsearch内部のサーキットブレイカーが発動して100%検索が失敗する障害がありました。 障害発生後、問題の起きたテナントではElasticsearchでの運用を短期的に諦めてSolrへの切り戻しAPIを実行して一時凌ぎをしています。 その後、調査・改修を行い、ふたたびElasticsearchへの切り替えと安定稼働に成功しています。

ストラングラーパターンはバグや性能問題が見つかったときの対応として非常に役に立ちました。 問題が起きても策を講じて粛々対応、粘り強く移行を最後まで遂行できました。

私の知る限りでは社内では初採用のパターンでした。このパターンを使った感想は、 レガシーソフトウェアと折り合いをつける方法として有用性が高く、強力な一手という感想です。

そしてNecoへ

今のプロジェクトの状況としては命からがら緊急的なエスケープから一時しのぎに成功し小康状態にこぎつけた状態です。

今の構成で長期間とどまり続けるのは危険なのでNeco(kubernetes)の上に移行する必要があります。 レガシーSolr脱却後、Neco移行とあわせて最適なElasticsearchの構成に改善作業・移行の準備中です。

移行計画の構想通りほぼ成功し、全体として驚くほどうまく移行できました。 しかしながら、移行の中で想定外のことや考慮が足りなかった点もあります。この辺りの改善点は次のNeco移行完了時にお話できればと思います。

最後に

貴重な時間を割いてここまで読んでいただいたみなさまの気分転換にすこしでもなれば幸いです。

Solr時代含め魔窟と戦い続けてくれたすべての人に感謝の意を示します。そしてこのプロジェクトに直接的、間接的にかかわったみなさまに感謝します。

記事執筆およびプロジェクト構想は@yokotasoが担当しました。 プロジェクトにかかわってくれたメンバーの協力、最後まで躊躇せずアクセルを踏み続けたインフラチーム責任者の理解、 意思決定をする人たちのリスクを判断したうえでの承認など、どれひとつ欠けても、全文検索すら提供できなっていた可能性があります。

最後に感謝の意と、プロジェクトメンバー(自分含む)が圧倒的昇給を勝ち取ることを願いつつこのプロジェクトの完了報告としたいと思います。

いつ壊れてもおかしくない状況で最後まで動き続けてくれたSolrにありがとう。そして、さようなら。

参考

Viewing all 692 articles
Browse latest View live