Cookpad Pad 2 という自作キーボードノベルティをつくった話

こんにちは、高井です。みなさま Cookpad Online Summer Internship 2020、お疲れさまでした。

さて、今回はインターンのノベルティとして Cookpad Pad 2 という自作キーボードキットをノベルティとしてつくったので、その紹介と解説です。今年のインターンシップはリモート開催ということもあり、ふだんよりも豪華なノベルティをつくることができました。写真では見えませんが、裏側に Cookpad Online Summer Internship 2020 のロゴがプリントされていて、限定感を出しています。

f:id:takai_naoto:20200910181821j:plain

というわけで、本記事では Cookpad Pad 2 を例に取りながら、自作キーボードキットを作成する方法について解説します。キーボードの開発はさまざまなノウハウが公開されているため、実際のところそれほど難しくはありません。本記事ではキーボード開発についての完全な手順を説明するというよりも、有益な記事やリソースを紹介することで、全体の流れとして個々のノウハウを結びつけることを目指します。

キーボードを設計、開発するときの流れは次のようなものです。この流れに沿いながら、どのようにキーボードを開発するのかを見ていきましょう。

  • プロダクト構想
  • キーボード基板設計
    • 回路図設計
    • パーツ選定
    • プリント基板設計
  • キーボードケース設計
    • 設計
    • 検証(アセンブリ)
  • 製造
    • キーボード基板
    • キーボードケース
  • ファームウェア開発
    • QMK Firmware
    • VIA

また、Cookpad Pad 2 はオープンハードウェアとして GitHub でプロジェクトを公開しています。記事とあわせてこちらも参照してください。

https://github.com/cookpad/cookpad-pad/tree/cookpad-pad2

プロダクト構想

まず、最初に行なうのは、どのようなキーボードをつくるのか構想することです。Cookpad Pad 2 は 6 キーのマクロパッドです。デフォルトでは「C」「O」「K」「P」「A」「D」の 6 文字が打てるので、「cookpad」と打つときに便利なキーボードです。初代のモデルは、2019年頃に製作をして、ノベルティとして個人的に配布をしていました。

デザインは、この初代 Cookpad Pad を踏襲するものとします。 6 キーを格子状に配置したシンプルなデザインです。また、ケースは PCB を 2 枚のプレートで挟み込んだ、サンドイッチマウント構造にします。この構造は自作キーボードでよく採用される構造で、安価に作成できることがメリットです。

それから、コネクターとして USB Type-C を採用します。そのために、 Pro Micro というマイコンボードを利用しない方針とします。 Pro Micro は自作キーボードに必要な電子部品が実装されているので、基板の設計を単純にすることができます。一方で、Micro USB Type-Bコネクタが採用されていたり、ピンヘッダでマイコンボードを取り付けるため、設計上の制約がでてしまうという欠点があります。そこで、 Pro Micro の利用をするのではなく、直接基板に同等の機能を実装していきます。

キーボード基板設計

ここまで決まったら、基板の設計を行なっていきます。今回は構造が単純なサンドイッチマウントを採用しているため基板を先に設計しますが、アルミ切削ケースなどの場合はケースの設計から先に行なった方がよいでしょう。

基板の設計には KiCad をつかいます。 KiCad はフリーソフトウェアとして配布されているEDAツールです。独特の操作性を持つので、はじめての人であれば、「KiCadことはじめ」などを参考に、一通り触ってみてください。

回路図

キーボードの基板設計については「ai03's Keybaord PCB Designer Guide」を参照してください。 KiCad の利用方法から回路設計、PCB設計まで一通りが解説されているので、これを読むだけでもキーボード設計ができるようになります。

自作キーボードの MCU は ATmega32u4 を利用することが一般的ですが、今回は ATmega32u2 を利用しています。ATmega32u2 の方が若干フットプリントが小さいため、部品配置が楽になるかもしれないというのが選定理由です。どちらもキーボードファームウェアとして広く利用されている QMK Firmware でサポートされている MCU です。特に理由がないのであれば ATmega32u4 を採用するのがよいとおもいます。

その他、TVSダイオードによる静電気放電対策やポリスイッチによる過電流対策を行なっています。キーボードのUSB-C関連の設計については、ai03氏が中心となって公開している Unified Daughterboard Project が参考になるでしょう。今回のキーボードでは、それよりも簡略化された実装にしています。

回路図シンボルなども ai03氏が公開しているものを利用するのが便利です。ai03氏、いったい何者なんだ……。

実際の回路図は下記のようなものになります。左上がMCU関連、右上がキーマトリクス、左下がUSB関連、右下が電源関連となります。

f:id:takai_naoto:20200910181904p:plain

なお、このサイズのキーボードであれば、MCUのピンとスイッチを一対一で対応させればよいので、本来ならばキーマトリックスは不要です。このプロジェクトを拡張することで、好きなキーボードつくってもらえるようにという意図を込めてキーマトリクスを採用しています。そのとき、「KICADの他のプロジェクトから回路をコピーする」のやり方でコピーすると便利でしょう。

パーツ選定

プリント基板設計に進むにあたって、パーツの選定をします。今回は基板製造を Elecrow へ依頼するつもりでしたので、Elecrowが在庫しているElecrow Parts Libraryや、部品の取り寄せができるDigi-Key Electronicsなどから部品を選定します。

パーツの選定は既存のキーボードで利用されている部品から選定するのがよいでしょう。ai03氏のリポジトリからUnified Daughterboard ProjectKBD8X MKIIOrbit などを参考にしています。USB-C のコネクタは TYPE-C-31-M-12 を採用します。この部品は、USB2.0にのみ対応したコネクターで、その分安価です。

それから、コンデンサや抵抗など一般的な部品は、Elecrowの方で代替品に差し替えてもいいかと確認されることがあります。そちらの方がコスト的にもメリットがあるので、それらのパーツについては仕様を示すくらいの気持ちで選定しています。

Component Package Footprint URL
Ceramic Capacitor, 22pF 0603 Capacitor_SMD:C_0603_1608Metric Elecrow Parts Library
Ceramic Capacitor, 1uF 0603 Capacitor_SMD:C_0603_1608Metric Elecrow Parts Library
Ceramic Capacitor, 0.1uF 0603 Capacitor_SMD:C_0603_1608Metric Elecrow Parts Library
Ceramic Capacitor, 10uF 0603 Capacitor_SMD:C_0603_1608Metric https://www.digikey.com/product-detail/en/murata-electronics/GRM188R61E106KA73D/490-18214-2-ND/9867922
Diode, Generic SOD-123 Diode_SMD:D_SOD-123 https://www.digikey.com/product-detail/en/micro-commercial-co/1N4148W-TP/1N4148WTPMSCT-ND/717311
TVS Diode, 5.5V SOT143B random-keyboard-parts:SOT143B https://www.digikey.com/product-detail/en/nexperia-usa-inc/PRTR5V0U2X215/1727-3884-1-ND/1589981
Polyfuse, 500mA hold, 1A trip 1206 Fuse:Fuse_1206_3216Metric https://www.digikey.com/product-detail/en/bel-fuse-inc/0ZCJ0050AF2E/507-1803-1-ND/4156312
Resistor, 5.1k 0805 Resistor_SMD:R_0805_2012Metric https://www.digikey.com/product-detail/en/panasonic-electronic-components/ERA-6AEB512V/P5-1KDACT-ND/1465964
Resistor, 22 0805 Resistor_SMD:R_0805_2012Metric https://www.digikey.com/product-detail/en/panasonic-electronic-components/ERA-6AHD220V/P123893CT-ND/9467822
Resistor, 10k 0805 Resistor_SMD:R_0805_2012Metric https://www.digikey.com/product-detail/en/panasonic-electronic-components/ERA-6AEB103V/P10KDACT-ND/1465971
Low Profile Tactile Switch 5.2x5.2mm  5.25.21.5mm random-keyboard-parts:SKQG-1155865 https://www.digikey.com/product-detail/en/c-k/RS-187R05A2-DS-MT-RT/CKN10361CT-ND/2747199
ATMEGA32U2 Microcontroller 32-TQFP 7x7mm Package_QFP:TQFP-32_7x7mm_P0.8mm https://www.digikey.com/product-detail/en/microchip-technology/ATMEGA32U2-AU/ATMEGA32U2-AU-ND/2187167
Crystal, 16MHz 3.2x2.5mm, 4 pad Type-C:HRO-TYPE-C-31-M-12-Assembly https://www.digikey.com/product-detail/en/CX3225SB16000D0GZJC1/1253-1698-1-ND/5995245/
HRO-TYPE-C-31-M-12 8.94x7.3mm Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm https://lcsc.com/product-detail/USB-Type-C_Korean-Hroparts-Elec-TYPE-C-31-M-12_C165948.html

プリント基板設計

回路が決まり、部品も決まりましたので、あとは PCB に配置していくだけです。配線をしているとパズルを解いているような気分になります。どのようにしたら効率的で美しい配線ができるのか、工夫の見せどころです。KiCad Pcbnew をつかって設計をしていくのですが、細かい操作方法についてはここでは説明しません。前述の「KiCadことはじめ」や「ai03's Keybaord PCB Designer Guide」を参照してください。

最初に ai03 - Keybaord Plate Generator をつかって、スイッチプレートを生成します。 Keyboard Layout Editor の Raw Data をコピーアンドペーストするだけでスイッチプレートを生成することができます。ファイルは DXF 形式でダウンロードできます。

次に、 KiCad Pcbnew にスイッチプレートファイルをインポートします。プレートにあわせてキースイッチのフットプリントを配置していきます。それから、その他の部品を配置します。USB 周辺や MCU 周辺から配線をしていくとよいでしょう。最後に、キーマトリクスをつくっておしまいです。このプロセスは、あまりキーボードに特有なことはありません。電源とGNDのパターンは太くする、水晶発振器はできるだけMCUに近いところに配置する、バイパスコンデンサは電源ピンの近くにする、USBは差動ペア配線なのでパターンを可能な限り平行に行なう、などに注意をして配線します。

f:id:takai_naoto:20200910181942p:plain

その他、ちょっとしたノウハウとなりますが、キー数が多いときにはスイッチ毎にダイオードを配置するのが面倒なときがあります。KiCad PcbnewはPythonでのスクリプティングが可能ですから、次のようなスクリプトをつくって配置しています。定数を変更してつかってみてください。

import pcbnew

START    = 1
STOP     = 42

X_OFFSET = 5937250
Y_OFFSET = 4857750
ORIENTATION = 90 * 10

board = pcbnew.GetBoard()

for i in xrange(START, STOP + 1):
    mx = board.FindModuleByReference("MX%d" % i)
    mx_pos = mx.GetPosition()

    new_pos = pcbnew.wxPoint(mx_pos.x + X_OFFSET, mx_pos. y + Y_OFFSET)

    diode = board.FindModuleByReference("D%d" % i)
    diode.SetPosition(new_pos)
    diode.SetOrientation(ORIENTATION)

    print("D%d moves to (%d, %d)" %(i, new_pos.x, new_pos.y))

pcbnew.Refresh()

キーボードケース設計

設計

次にサンドイッチマウントのケースをつくっていきます。サンドイッチマウントとは、PCBをスイッチプレートとボトムケースで挟み込むような構造です。キーボードのマウント方式については「Cheat sheet: Custom keyboard mounting styles 」がよくまとまっています。

今回は、サンドイッチマウント構造でもPCBを2枚のプレートによって挟み込む、よりシンプルな構造です。当初、プレートにはアクリルを採用しようと考えていたのですが、ご時世によりアクリルが品薄になっていたため、プリント基板(FR4)で製作することにしました。

ケースの設計は Autodesk Fusion 360 をつかいます。 Fusion 360 の基本的な操作については「Fusion 360 で簡単なケースを作る(初心者向け) - Self-Made Keyboards in Japan」などを参照してください。また、公式サイトの 入門セミナー も参考になるでしょう。

まず、スイッチプレートのDXFファイルを読み込み、プレートを支えるための M2 スペーサーのスペースをつくります。廣杉計器 M2 スペーサー の直径は 4mm ですので、キースイッチと干渉しないようにします。

f:id:takai_naoto:20200910182015p:plain

それから、「押し出し」て、 M2 ねじを通すために 2.2mm くらいで「穴」を作成し、「フィレット」で角を丸めます。できた面にスケッチを作成して、それを DXF としてエクスポートすればスイッチプレートの完成です。ボトムプレートも同じような構造ですので、コピーをして履歴を編集すれば簡単に作成することができます。

f:id:takai_naoto:20200910182038p:plain

検証(アセンブリ)

PCB とケース用のプレートの設計が完成したので、実際に組み立てて問題が起こらないか確認してみましょう。KiCad では基板の3Dデータを STEP ファイルを出力することができます。これを Fusion 360 に取り込み、先程つくったプレートファイルと組み合わせます。

スイッチプレートとPCBの間隔は 3.4 mmです。これは、Cheryr MX シリーズのデーターシート を確認するとボトムハウジングの高さは 5 mm ですので、ここからプレートの厚みである 1.6 mmを引いた数字です。ボトムプレートとPCBの間隔は、PCBに実装されている USB-C コネクターの高さが 3.21 mmですから、それ以上あればよさそうです。 9 mm のスペーサーを利用することを考えて 4mm にします。

完成したら、部品の干渉など問題が発生していないことを確認します。

f:id:takai_naoto:20200910182057p:plain

製造

キーボード基板

基板の製造と電子部品の実装は Elecrow に発注します。 Elecrow は深圳にある工場で、品質と価格のバランスが良いことから、製品の製造のときにはいつも利用しています。発注にあたっては、 KiCad でガーバーフォーマットのデータを作成します。オプションを次の画像のように選択します。さらに、ドリルファイルも作成します。

f:id:takai_naoto:20200910182121p:plain f:id:takai_naoto:20200910182144p:plain

さらに、 Elecrow では生成されたファイルの拡張子を変更する必要があります。この作業は面倒で間違いやすい作業ですので、私はちょっとしたスクリプトを利用して拡張子の変更を行なっています。拡張子を変更したら、ガーバーデータをZip形式でアーカイブします。

#!/bin/bash

PROJECT=$1
OUTPUT=elecrow

mkdir "$OUTPUT"

cp "$PROJECT"-F_Cu.gtl      "$OUTPUT"/"$PROJECT".GTL
cp "$PROJECT"-B_Cu.gbl      "$OUTPUT"/"$PROJECT".GBL
cp "$PROJECT"-F_SilkS.gto   "$OUTPUT"/"$PROJECT".GTO
cp "$PROJECT"-B_SilkS.gbo   "$OUTPUT"/"$PROJECT".GBO
cp "$PROJECT"-F_Mask.gts    "$OUTPUT"/"$PROJECT".GTS
cp "$PROJECT"-B_Mask.gbs    "$OUTPUT"/"$PROJECT".GBS
cp "$PROJECT"-Edge_Cuts.gm1 "$OUTPUT"/"$PROJECT".GML
cp "$PROJECT"-PTH.drl       "$OUTPUT"/"$PROJECT"-PTH.TXT
cp "$PROJECT"-NPTH.drl      "$OUTPUT"/"$PROJECT"-NPTH.TXT

次に Excel で発注書をつくります。 Elecrow でも 発注テンプレート を用意していますし、実際に発注するときに利用したファイルもリポジトリに入れておきますので、参考にしてください。

Zip形式でアーカイブしたガーバーファイル、Excelの発注書を添付して service@elecrow.com へメールで発注をします。このとき、配送方法についても指定をするとよいでしょう。英語については自信がないのですが、下記のような文面でいつも送っています。

To whom it may concern,

I'd like to order PCBA service. Could you give me a quotation for attached files? The shipping address is as follows. I would prefer OCS as a shipping method.

XXXXX XX-XX-XX, XXXX, Tokyo XXX-XXX, Japan

Best Regards,

ほどなく担当者から見積が来るので、内容に問題がなければ PayPal で支払いをします。製造には、だいたい1ヶ月くらいかかるようです。製造のプロセスで何か問題が見付かれば、それも連絡してくれます。私は、ガーバーファイルでのフットプリントと部品のパッケージが一致しないミスをよくします。

キーボードケース

今回は FR4 、つまり PCB でプレートをつくるので、 KiCad でデータを作成します。 DXF ファイルをインポートするときにグラフィックレイヤーで Edge.Cuts 指定すれば、外形情報として取り込むことができますので、そのデータをもとにガーバーフォーマットのデータを書き出します。

発注は Elecrowのフォーム から行ないます。プレートの厚さはデータシート上で 1.5mm なので、PCBではその厚さに近い 1.6mm を選択します。こちらは1週間ほどで届きます。

アクリルプレートを発注するときは、レーザー加工サービスによって指定のテンプレートがありますので、そのフォーマットに従って発注をします。個人的な利用であれば工房Emerge+のレーザー加工サービスを利用することが多いです。Illustrator形式ファイルのテンプレートにDXFファイルを取り込み、データを作成します。アクリルは 2mm のものを選びます。フォームから見積を依頼したうえで支払いをすると、こちらも1週間ほどで届きいます。

ファームウェア開発

ハードウェアができたらファームウェアをつくっていきましょう。今回は最低限キーボードとして動作するための実装を目指し、 QMK Firmeware や VIA のリポジトリに取り込んでもらうことを考えないようにします。

QMK Firmware

ファームウェアはフリーソフトウェアとして開発されている QMK Firmware を利用します。QMK Firmware の開発は、事前にセットアップが必要です。公式ドキュメントの「Setting Up Your QMK Environment」を読み、セットアップをしてください。

QMK Firmware のトップディレクトリで util/new_keyboard.sh を実行すると、 keyboards ディレクトリ以下にテンプレートができますので、そのファイルを編集していきます。Cookpad Pad 2 の差分 を例にとりながら、編集する部分を見ていきましょう。

config.h:23-28 ではUSBのベンダーID、プロダクトIDを定数として定義します。USBのベンダーID、プロダクトIDについては衝突しないことが求められていますが、テスト目的のIDなどは定義されていません。なので、「いい感じにする」必要があります。詳しくは「 USBのベンダーIDとプロダクトIDの話 」などを参照してください。

#define VENDOR_ID 0xFEED
#define PRODUCT_ID 0x9009
#define DEVICE_VER 0x0002
#define MANUFACTURER Cookpad Inc.
#define PRODUCT Cookpad Pad
#define DESCRIPTION A six keys macro pad made by Cookpad.

config.h:31-45 では、キーマトリクスの定義を行ないます。Cookapd Pad は 2行 × 3列 のマトリクスですので、そのように定義をします。さらに回路図を確認し、ピンとの対応も定義します。

#define MATRIX_ROWS 2
#define MATRIX_COLS 3

...

#define MATRIX_ROW_PINS { C6, C7 }
#define MATRIX_COL_PINS { B7, B6, B5 }

Cookpad Pad 2 は MCU として ATmega32u2 を利用していますので rules.mk:2 の部分も変更します。 ATmega32u4 であれば変更は必要ありません。

MCU = atmega32u2

cookpad_pad.h:29-36 はキーボードの物理配列の定義です。キーボードによっては、行と列の二次元配列として定義されているキーマトリクスの全てが入力可能というわけではありません。そこで、キーボードの物理配列とキーマトリクスを対応させるためのマクロを定義します。

#define LAYOUT( \
    K00, K01, K02, \
    K10, K11, K12  \
) \
{ \
    { K00, K01, K02 }, \
    { K10, K11, K12 }  \
}

keymaps/default/keymap.c:18-23 で、キーボードの論理配列のデフォルト値を定義します。キーコードについては「Full List」を参照してください。

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [0] = LAYOUT(
        KC_C, KC_O, KC_K,
        KC_P, KC_A, KC_D
    )
};

変更が完了したらビルドが通るかどうかを確認してください。ファームウェアを書き込むには工場出荷時に書き込まれているブートローダのDFU機能をつかって、USB経由で書き込みをします。

$ make cookpad_pad:default
$ make cookpad_pad:default:dfu

VIA

VIA は QMK を前提につくられたキー設定ツールです。それまで、 QMK Firmware でキーレイアウトを変更するためには、プログラムを修正してコンパイルする必要がありました。 VIA を使うと、 GUI でコンパイルなしにキーマップを変更することができます。

f:id:takai_naoto:20200910182237p:plain

キーボードを VIA に対応させるためには QMK Firmware で VIA に対応したキーマップを作成する必要あります。対応する方法については公式サイトの「Configuring QMK」や「(設計者向け)VIA対応のファームウェアを作ろう」などを参照してください。

Cookpad Pad 2 でいうとkeymaps/via/rules.mk が VIA 対応となります。主だったものは VIA_ENABLE だけです。その他は、 VIA では対応していない機能を無効にしたり、ファームウェアのサイズを減らすためのオプションとなります。

VIA_ENABLE = yes
LINK_TIME_OPTIMIZATION_ENABLE = yes
EXTRAKEY_ENABLE = no
MOUSEKEY_ENABLE = no
CONSOLE_ENABLE = no
MIDI_ENABLE = no

それから、 VIA でつかう cookpad_pad.json も用意します。便宜上、 QMK のリポジトリに含めていますが、このファイルを含んだままプルリクエストを送ると、余分なファイルがあるとレビューで指摘されるので注意してください。

{
    "name": "Cookpa Pad",
    "vendorId": "0xFEED",
    "productId": "0x9009",
    "lighting": "none",
    "matrix": { "rows": 2, "cols": 3 },
    "layouts": {
        "keymap": [
            ["0,0","0,1","0,2"],
            ["1,0","1,1","1,2"]
        ]
    }
}

まとめ

本記事では Cookpad Pad 2 を事例として、キーボードの基板設計からケース設計、製造、ファームウェア開発について簡単に解説をしてきました。実際のところ、まだまだ細かな解説をする部分はありますが、大まかな流れについては理解いただけたのではないでしょうか。

Cookpad Pad 2 は オープンハードウェアとして公開 していますので、このプロジェクトを改変して新しいキーボードをつくってみることにチャレンジしてみてください。

Rubyの開発を支える技術

こんにちは、遠藤(@mametter)です。RubyKaigi Takeout 2020お疲れさまでした。

現在クックパッドには、フルタイムでRubyの開発をしている人が2人います(笹田と遠藤)。 それぞれ、Ruby 3の目標である並列性と静的解析の実現をメインミッションに据えて活動していますが、実はそれ以外にもRubyの開発を支えるための活動をいろいろやっています。

今回は、遠藤が関わっている範囲で、「Ruby開発者会議を支える技術」「Ruby開発のリモート議論を支える技術」「Rubyの品質を支える技術」についてざっと紹介してみます。

1. Ruby開発者会議を支える技術

Rubyに対する機能提案などの議論は、原則として、バグトラッカ上で行われます。 しかし、設計者であり最終決定権を持つmatzの多忙などの理由で、それだけでは議論が停滞してしまうのも事実です。 そこでRubyでは、開発促進のために月例で開発者会議を行っています。 私はここ数年、この会議を運営することにコミットしています。

会議のプロセス

毎月、次のことを行っています(検討以外はだいたいすべて私がやってます)。

  • 開催の約1ヶ月前に議題を募集するチケットを作る(例:先月のチケット)。
  • 開催の数日前、挙げられた議題をmarkdownにまとめ、有志のコミッタとともに事前検討をする(準備会)。
  • 会議当日、matzおよび有志のコミッタと本検討をする。結論の出た提案については、なるべくその場でmatzに回答してもらう。
  • 開催の数日後、議事録を清書して公開する(例:先月の議事録)。

コミュニケーション技術

会議当日のコミュニケーションには、クックパッド提供のzoomと、オンラインmarkdown共同編集ツールであるhackmdを使っています。 コロナ以前は東京近郊でのオフライン会場もありました(matzは大体リモート参加)が、今年は完全オンラインです。 アジェンダおよび議事録の共同編集はいろいろ試した末、「リアルタイムの共同編集ができ、markdownで書けて、コード断片も書きやすい」ということでhackmdに落ち着いています。

準備会

当日の会議は午後半日、約5時間をかけていますが、20件程度の議題をさばくには十分とは言えず、効率化が課題でした。 そのため今年から、数日前に有志での事前検討を行っています。 これにより、当日の参加者が誰も議題を理解していない、という非効率が回避できるようになりました *1 。 またこのフェーズで、事前に担当者に話題を振っておいたり、議題が曖昧なチケット・matz判断が不要そうなチケットに事前にフィードバックを返したりもします。 これにより、当日に議題をさばき切れないことはだいぶ減りました。

非互換の影響検討

仕様変更を議論する際には、かならず非互換の影響が議論されます。かつては互換性を気にしない言語とたびたび揶揄されていたRubyですが、現代ではかなり気を使うようになりました。

たとえば先日から、set.rbを組み込みにするという提案がたびたび議論されています(結論は出ていない)。 こういうとき、「トップレベルにSetクラスを自力定義している人は実際どのくらいいるか?」というような疑問が浮かびます。 このとき役に立つのがgem-codesearchです。

gem-codesearchがセットアップされているコミッタ用共用サーバにログインし、次のようにすることで、全最新版gemに対する高速grepができます。

$ gem-codesearch "^class Set$"
/srv/gems/ConstraintSolver-0.1/lib/extensions.rb:class Set
/srv/gems/GoNodes-0.0.1/lib/monkeypatch/set.rb:class Set
/srv/gems/Narnach-minitest-0.3.3/lib/set_ext.rb:class Set
/srv/gems/Ron-0.1.2/lib/ron.rb:class Set
/srv/gems/acapela-0.8.1/script/create_voices.rb:class Set
/srv/gems/annlat-0.0.1/lib/annlat/LaRuby.rb:class Set
/srv/gems/antlr4-0.9.2/lib/antlr4/base.rb:class Set
...

もちろん、この検索対象はあくまで公開gemだけなので、非公開アプリケーションのコードでどうかはわかりません。一方ここでコンフリクトする例が1つでも見つかったら諦めるというわけでもありません。それでも、非互換の影響の見積にはそうとう腐心しています。

なお、gem-codesearchはバックエンドにgoogle/codesearchを利用しています。codesearchはインデックスサイズに4GB制限があり、最近gemのコードサイズがこの制限に触れてしまったので、頭文字のアルファベット26文字+非アルファベット文字の27個にインデックスを分割するという延命をしたりしました(google/zoektも試しましたが、検索の起動がかなり遅く感じたので)。

クレジット:gem-codesearchはakrさんが開発し、hsbtさんがメンテナンスしています。コミッタ用共用サーバはRuby Associationのご提供で、gem-codesearchは私が管理しています。

dev-meeting-bot

議題を募集するチケットはコピペと手編集で作っていましたが、毎月となると、意外と重労働でした。 そこで、Slackボットを作りました。上述のチケットは、このボットにポンと作らせています。

他にも、最新の会議チケットを教えてくれる機能や、参加者にzoomやhackmdのURLをメールする機能が付いています。

クレジット:dev-meeting-botを含め以後紹介するSlackボットの多くは私が開発していますが、チケット作成機能は yebis0942 さんや znz さんのコントリビューションです。また、Herokuが計算機リソースを提供してくれています。

2. Ruby開発のリモート議論を支える技術

開発者会議以外でも、我々は日々Rubyの開発のための議論を行っています。 必要に応じてzoom(クックパッド提供)を使うこともありますが、ほとんどはSlack上での議論です。 そのためRuby開発者のSlackでは、議論を円滑にするためにいろいろなボットがうごめいています。 ざっとご紹介。

クレジット:Ruby開発者のSlackはRuby Associationにご提供いただいています。

all-ruby

all-rubyとは、これまでにリリースされたほぼすべてのruby(0.49から最新版まで)をビルドするスクリプト、およびその生成実行ファイルを集めたDockerイメージです。 みなさんのお手元でも、次のようにdocker pullして試すことができます。

$ docker pull rubylang/all-ruby
$ docker run --rm -ti rubylang/all-ruby
root@50d2785e9b39:/all-ruby# ./all-ruby -e 'puts "Hello"'
ruby-0.49             -e:1: syntax error
                  exit 1
ruby-0.50             -e:1: syntax error
                  exit 1
ruby-0.51             -e:1: undefined method `puts' for "main"(Object)
                  exit 1
ruby-0.54             -e:1:in method `puts': undefined method `puts' for "main"(Object)
                  exit 1
ruby-0.55             -e:1: undefined method `puts' for "main"(Object)
                  exit 1
...
ruby-0.76             -e:1: undefined method `puts' for "main"(Object)
                  exit 1
ruby-0.95             -e:1: undefined method `puts' for main(Object)
                  exit 1
...
ruby-1.0-961225       -e:1: undefined method `puts' for main(Object)
                  exit 1
ruby-1.0-971002       -e:1: NameError: undefined method `puts' for main(Object)
                  exit 1
...
ruby-1.0-971225       -e:1: NameError: undefined method `puts' for main(Object)
                  exit 1
ruby-1.1a0            -e:1: NameError| undefined method `puts' for main(Object)
                  exit 1
ruby-1.1a1            -e:1: NameError| undefined method `puts' for main(Object)
                  exit 1
ruby-1.1a2            -e:1: NameError:undefined method `puts' for main(Object)
                  exit 1
ruby-1.1a3            -e:1: NameError: undefined method `puts' for main(Object)
                  exit 1
...
ruby-1.1a9            -e:1: NameError: undefined method `puts' for main(Object)
                  exit 1
ruby-1.1b0            Hello
...
ruby-2.7.1            Hello
root@50d2785e9b39:/all-ruby#

Kernel#putsが入ったのはruby-1.1b0からだとわかりますね。このように、Rubyの仕様変更やバグを議論しているとき、どのバージョンから変わったのかを調べるのに役に立ちます。

これをより手軽に・議論中に試すために、Ruby開発者のSlackには「all-rubyボット」がいます。

all-ruby-bot

ボットのソースコードは公開されているので、興味がある人は設置してみてください(dockerコマンドが起動できて、かつSlackからのWebhookが受けられる計算機が必要です)。

クレジット:all-rubyはakrさんが作り(akrさんによる発表資料)、dockerイメージはhsbtさんがメンテナンスしています。Slackボットは私が作っています。

rubyfarm

all-rubyはリリース版ごとの実行ファイルでしたが、コミットごとの実行ファイルをためたイメージも作っています。 それがrubyfarmです。

これは、rubyfarm-bisectというgemから使うことを想定しています。 git bisectは、挙動が変わった(エンバグした・バグ修正された)タイミングをコミット単位で特定できるツールですが、rubyを毎回ビルドするのは比較的大変でした。 rubyfarm-bisectを使うと、コミットごとにビルドではなくdocker pullで済むので、待ち時間が大幅に減り、試行錯誤も容易になります。自動git bisectはいろんな理由で失敗することで有名なので、ビルド失敗がない&試行錯誤がやりやすい、というのはだいぶストレスを下げます。

ただ、Docker Hubがストレージを制限する方針を発表したので、非公開ローカルのレジストリに移行する予定です。

commit-link

挙動変更のあったコミットを特定できたら、それについてSlackで議論をするでしょう。 そのとき、コミットハッシュをコピペするだけでは、読む人が自分でgit showにかけたり、GitHubのリンクをたどったりする必要があって、ストレスフルでした。

そこで、Ruby開発者のSlackにはcommit-linkというボットがいます。

commit-botは全発言をウォッチしていて、10桁以上のコミットハッシュっぽいものがあったらGitHubを見てコミットが実在するか確認し、発見したらリンクを貼ってくれます。 地味ですが、非常に便利です。

その他

他にも、標準添付ライブラリのメンテナを教えてくれる"who-is-maintainer"や、脆弱性報告を議論する専用チャンネルを作ってくれる"h1-channel-creator"など、いろいろなボットがRuby開発議論を支えています。

3. Rubyの品質を支える技術

最後に、Rubyの品質を高めるためのCIの活動について。

Rubyには、CIがたくさんあります。 私が把握している限りで、GitHub Actions、Travis、AppVeyor CI(Windows)、弊社笹田による独自CI、そしてrubyci.orgです。 このようになっているのは(歴史的経緯もありますが)テストの目的がいろいろあるためです。 たとえば笹田独自CIは、まれにしか発生しないハイゼンバグ(GCやVM最適化周りでしばしば発生する)を洗い出すために同じコミットを何度も繰り返しテストし続けます。

rubyci

rubyci.orgは、たぶんRubyで一番古くからあるCIです。

もともとは、chkbuildというRuby専用のテスト実行ツールがあり、有志が自分の環境でchkbuildを実行した結果をキュレーションしたのがrubyci.orgでした。 しかし現在では、通常のCIはGitHub ActionsやTravisで十分になったので、主な特徴が変わっています。

  • GitHub Actionsにないような、ややマイナーな環境もカバーしている
  • コミッタは(ほとんどの)テスト環境に直接sshログインでき、デバッグができる
  • テスト中に表示される警告数も監視している

とくに、再現性の低いバグと戦わなければならないRubyコア開発においては、2番目の特徴は重要になっています。

現在では多くのテスト環境はAWS EC2インスタンス上で動作しています。これらの環境をセットアップしたり、コミッタのアカウントを自動定義したりするために、mitamaeを使った自動化もなされています(ruby/ruby-infra-recipe)。

クレジット:chkbuildはakrさんが作り、rubyci.orgはnaruseさんが作りました。その後のメンテナンスは主にhsbtさんと私が行っています。mitamaeによるプロビジョニングはhsbtさんが整理しました。AWSの費用はRuby Associationがカバーしてくれています。他にも、一部の環境はご提供を受けています(rubyci.org末尾のスポンサーリストを参照のこと)。

alerts-emoji

CIの結果を監視するのは苦行なので、みんなSlackなどへの通知を活用していると思います。 しかしRubyには多数のCIがあり、それぞれ好き勝手なフォーマットで通知を投げていたので、そのチャンネルを眺めて理解するだけでも苦行となっていました。

また、テスト実行に数時間かかる環境などもあるため、ちょっと古いコミットに対する通知が遅れてくることが多く、一体どのコミットに対する通知なのかパッと見でわからない、という問題もありました。

そこで、すべての通知を集約し、統一フォーマットで通知するようにしました。 また、どのコミットに対する通知かを視覚的にわかりやすくするため、コミットに適当な幾何学図形を割り当てることにしました。

なお、絵文字については非常に議論がありました。 私は10桁程度の16進数をパッと見で比較するのが苦手 *2 なので、ランダムに選んだ絵文字を振るようにしました(絵文字のほうが視覚的に比較しやすい)。 しかしそうすると、絵文字から意味を読み取ってしまう人から、逆に認知コストが上がったという反対の声があがりました(たとえばスマイルマークや㊙のように意味のある絵文字)。 議論と試行錯誤の末、色違いの幾何学図形の絵文字を使うようにすることで、だいたいの人が満足できるようになりました。

クレジット:まず笹田が独自CI用の通知チャンネルを作りました。そのチャンネルに各種通知が参入して破綻したので、この統一チャンネルを遠藤が作り、その後k0kubunさんがいい感じに整理してくれました。

まとめ

Ruby開発は、多数の技術、および(自分を含めた :-)多数の人々によって支えられています。

ここに上げたものはあくまで遠藤がかかわっているものの一部だけで、上げきれなかったものもいろいろあります(すみません)。みなさんに感謝しながらやっています *3

*1:正確には、去年も私がすべての議題を理解するように努めていたのですが、丸一日消費してしまうことに加え、一人では議題を誤解したり、論点を洗い出せなかったりして、いまいちだったので、笹田と相談して準備会にトライすることになりました。

*2:ハッシュの桁数が、GitHub Actionsは7桁、rubyciは10桁、というように通知ごとにバラバラだったのが特に最悪でした。

*3:特にCIのAWS費用がなかなか大変なので、その費用をカバーしてくれているRuby Associationに寄付をいただけるとありがたいです :-)

Amazon RDS/Auroraをクローンするシステムを作った話

こんにちは、技術部SRグループの菅原です。

最近、Ninja650からNinja1000に乗り換えました。パワーがあるせいで3速発進・4速発進が平気でできてしまい、シフトワークがどんどん下手になっています。精進したいものです。

この記事では、Amazon RDS/Auroraをクローンするシステムを作った話を書きます。

Amazon RDS/Auroraをクローンするシステム

サービス開発を行っていると、調査や検証でプロダクション環境で使われているデータベースが必要になることがあります。開発環境やステージング環境にもデータベースは存在するのですが、プロダクション環境のデータでしか再現しないバグの調査や、プロダクション環境のデータ量でのスキーマ変更の負荷の検証など、開発環境やステージング環境のデータベースではできない作業も多いです。しかし、オペレーションミスや個人情報へのアクセスを考えると、プロダクション環境のデータベースで直接作業をすることは大きなリスクを伴います。

Amazon Auroraのクローン作成機能を使うと、プロダクション環境に影響を及ぼさないクローンを作成できるのですが、個人情報にアクセスできてしまう点は解決できません。また、クローンの作成や削除には強力なIAMの権限が必要なため、管理者がクローンを作成して利用者に渡すような手間が発生していました。

そこで、それらの問題を解決し、開発者が手軽にプロダクション環境のデータベースを触れるように、Amazon RDS/Auroraをクローンするシステムを作成しました。

クローン作成の手順は以下の通りです。

  1. SlackでRubotyに対して @ruboty rds clone db-cluster:my-cluster db.t3.small 4h というコマンドを送る
    • 普段からChatOpsでデプロイが行われていること、作成したクローンDBの情報を共有しやすいことなどからインターフェースとしてSlackを利用しました
  2. RubotyがBarbequeのジョブを起動する
  3. Barbequeのジョブがクローンを作成する
    • Auroraの場合はクローン作成機能、RDSの場合はスナップショットから復元
  4. クローンDBのマスターユーザーのパスワードを変更する
  5. クローンDBのデータをマスキングする
  6. セキュリティグループを変更して、社内ネットワークからクローンDBにアクセスできるようにする

f:id:winebarrel:20200819092303p:plain

f:id:winebarrel:20200819092422p:plain

データのマスキングには同僚の@mozamimyが作ったDumptruckという社内ツールを利用しており、以下のようなJsonnetの設定ファイルに従ってデータをマスキングします。

{
  database: 'db_name',
  except: ['secure_%'], // `secure_`プリフィックスのテーブルはクローンDBにコピーしない
  rules: [
    {
      table: 'users',
      transforms: [
        {
          column: 'tel',
          value: "lpad(id, 12, '0')", // SQLでデータをマスキング
          inline_sql: true,
        },
        {
          column: 'email',
          value: "concat(id, '@example.com')",
          inline_sql: true,
        },
      ],
    },
  ],
}

作成したクローンDBは、利用後に開発者が自分で削除するか、利用期限が切れるとバッチ処理が自動的にクローンDBを削除します。

f:id:winebarrel:20200819092447p:plain

開発者が自分でDBの削除やパラメータの変更を行えるようにするため、クラスタIDやインスタンスIDには rcc- というプレフィックスを付け、IAMの対象リソースをrcc-*とした権限を開発者に付与しています。

まとめ

Amazon RDS/Auroraのクローンが手軽にできるようになったことにより、プロダクション環境のデータの調査や、データベースのパフォーマンスの検証がはかどるようになりました。また、データのマスキングを設定ファイルで管理することにより、どのカラムに秘匿情報が入っているかもわかりやすくなったと思います。プロダクション環境のデータベースを使った作業は管理者やSREに作業が集中しがちなので、このような形でなるべく開発者に権限を委譲していきたいです。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/