今回はブランチごとにS3バケットのサブパスにStorybookをデプロイするため、Storybookで参照している画像などのメディアファイルを一緒にデプロイする場合、コードからファイルを参照するためには相対パスを設定する必要がありました。 最初は絶対パスで指定していたためアイコンが表示されない状態になり、トラブルシューティングが大変でした。(参考:Absolute versus relative paths)
ファーストビューに関係のない DOM のレンダリング処理が多く行われていたので、レンダリングを遅延させるようにしました。また、お知らせのレコード一覧では非同期なデータ取得とそのレンダリングによってレイアウトシフトが起こっていたので、表示エリアの高さをあらかじめ確保するようにして、レイアウトシフトが起こらないようにしました。
ここでは 2 つの部門(競技部門,作品部門)について,それから,この大会がいかにして IT エンジニアの育成に資する場であろうとしているかをお話しします.この記事を通じて,一人でも多くの方に興味を持っていただき,あわよくばその地方で大会を開きたいという声が上がり,自分も運営のお手伝いができると大変喜ばしいです.
競技部門と作品部門
コンテストは 2 部門構成です.地域によってはどちらかの部門だけ開催という形もあります.
競技部門
CHaser (チェイサー) という競技プラットフォームを利用した対戦を行います.これは全国情報技術教育研究会が行っている高校生向けのプログラミングコンテストで使用されていたもので 2010 年大会の後に使用許可をとり*3,U-16 プログラミングコンテストで使用しています.大会参加人数によって総当りであったりトーナメントであったり形式はそれぞれです.以下の画像は第10回U-16旭川プログラミングコンテスト・第7回U-16プログラミングコンテスト北海道大会の決勝トーナメントの様子です.
競技プラットフォーム CHaser 上での競技の様子
C : Cool(先攻) と H : Hot(後攻) がそれぞれの選手のプログラムにより動作し,アイテム(ダイヤモンド)を集めます.C と H のクライアントは Walk,Look,Search,Put の 4 コマンドに上下左右の方向をもたせた 16 種類の命令を駆使してマップ上を練り歩きます.勝敗については,自滅しないことや相手をブロックでつぶした場合など,攻撃等も考慮されて決定します.これらを踏まえて練られた競技ルールは全国統一ではありません.各地域で旭川大会のルールに手を加えたルールを使用しています.一つの例としてこちらの動画で旭川大会のルールがご覧いただけます.
この仕組みが出来上がって満足するだけでなく,別の地域にも広めていき,よりたくさんの IT エンジニアを目指す卵たちが学びうる環境を作ることも大事な活動です.また,継続可能な取り組みとする一環として高校生や高専生が講師として中学校に訪問する際は,お小遣い程度ではあるものの,実行委員会から日当もでます.若いうちからタダ働きに体が慣れるのはよくないので,これは重要な要素です.
函館で新たに開きたいという要望があったので富良野から講習会を開きにいきました 現在 8 道県でこの U-16 プログラミングコンテストの仕組みを使って,たくさんの学びの環境がつくりあげられています.以下は BCN AWARD 2021 のパンフレットからの引用です.また,ここには挙げられていませんが,実は北海道富良野市でも U-16 プログラミングコンテスト参加に向けた活動が 4 年間も続いています.西原は主にこの富良野市での活動を支援してきました.2020 年度の富良野勢の小さな表彰式は記事にもなっています.
2020 年度の大会開催地域
サイボウズの人として関わる
旭川大会では広告として A4 一枚のメッセージを出しました.製作は西原と同じコネクト支援チームの上岡(@ueokande)さんにお願いしました. 旭川大会に出したサイボウズのメッセージ プログラミングを学んだ先に IT エンジニアとしてのキャリアがある,という一例として児童生徒に我々の存在を認知してもらうことも大事なことです.プログラミングを始めたばかりのころは,技術自体ばかりに注目して学習を進めていくものだと思いますが,いずれ来る職業選択の際に IT エンジニアが候補に入るかどうかはそれだけでは決まらないかなと思っています.
こうして IT エンジニアへの一歩を踏み出した若者が,気楽に我々とともに技術で遊べる未来が楽しみで仕方ありません.もしかすると一緒に働くことになるかもしれません.今日明日ではない遠い未来ではありながら,日本全体の IT エンジニアの人数増加やレベル向上に資する活動であると信じて活動しています. ぜひ西原がやっているような勉強会にも参加してもらって,さらに交流機会を持ち,切磋琢磨したいものです.(彼ら自身が勉強会を開く未来も楽しい)
はじめに恵良さんから説明をいただきました.Developer Leading 部では開発者向けにkintone や Garoon など製品の技術関連情報を提供したり,認知活動を行っているとのことです.しばしばコネクト支援チームとも活動領域がかぶることもある部署になります.様々な媒体を活用して認知活動を広げる中で,王道である Web サイトや勉強会の展開では広がりに限界を感じることがあったというお話を伺いました.
西原も技術同人誌を書くにあたり,展開されている Web サイトである cybozu developer networkを見て回ったのですが,以下の例のような kintone に直接関係のない技術資料も用意されていて,ここまでコンテンツを作り込んだにも関わらず広まっていかないというのは確かに悲しいと思いました.
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).
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.
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.
my.cnf に設定を書くことができない問題の回避策として MySQL 8.0 の新機能である SET PERSIST ステートメントを使う方法があります。これは MySQL のシェルだけを使って設定項目を永続化(再起動後にも有効に)することができる機能です。my.cnf では default_collation_for_utf8mb4 を設定することができませんが、SET PERSIST では設定することができ、再起動後も設定を引き継ぐことができます。
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 を指定している箇所があり、どうすれば円滑に変数名を切り替えることができるか悩んでいました。
MySQL のバージョンによって変数名を変えるような分岐を作りたくなりますが、利用しているコネクターのライブラリによってはより良い方法が使える場合があります。たとえば Go の database/sqlなら BeginTxの引数でトランザクション分離レベルを指定することができます。こちらは変数名の変更の影響を受けません。そもそもトランザクション分離レベルはトランザクション単位で個別に設定されるものであるため、こちらの使い方のほうがコネクション単位でグローバルに設定するよりも望ましいかもしれません。
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 コミュニティのみなさまに感謝申し上げます。
余談ですが、パブリックリポジトリの場合、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↩
クラスタが構築される様子は 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>
$ 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 を使います。
こちらは更新が遅延していないレプリカインスタンスに負荷分散されるようになっています。
この問題を解決するため MySQL Routerという専用のソフトウェアがあるのですが、Kubernetes には宛先 Pod を動的に切り替えることができる Serviceという仕組みがあります。
Service を使うほうが構成コンポーネントを減らして簡略にできるため、MOCO は Service 方式を採用しています。
moco-<クラスタ名>-primary: 現在のプライマリインスタンスに繋がる Service
moco-<クラスタ名>-replica: データの反映が遅れてないレプリカインスタンスに負荷分散する Service
$ 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 が順番に削除されて作り直されるので、自動でスイッチオーバー処理を行うことでダウンタイムを極小化するわけです。
$ 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
$ 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 もサポートしていません。