RustでActivityPubサーバを書いていてハマったところ
ここ半年くらいMisskeyの某サーバに定住していて、裏で動いている仕組みが気になったので勉強がてらRustでActivityPubのサーバを書いた。といってもガチのアプリケーションではなく、複数のBOTユーザを抱えてフォロワーにノートを配信するだけの片方向な感じのやつ。
実装を進めていくと何回かどうやったらいいのかわからなくてハマったので備忘録として残す。
HTTP SignatureのDigestの求め方
sha256とbase64を使う。SHA-256のハッシュ値を2文字ずつに分割して16進数として解釈し、BASE64エンコードすればOK。なんかもうちょっとうまいやり方があるような気もする。
fn http_digest(data: String) -> String { let sha256_hash = digest(data); let binaries = sha256_hash.chars() .collect::<Vec<char>>() .chunks(2) .map(|c| c.iter().collect::<String>()) .map(|hex| u8::from_str_radix(&hex, 16).unwrap()) .collect::<Vec<u8>>(); return general_purpose::STANDARD.encode(binaries); }
HTTPリクエストの署名
Signatureヘッダに含める署名は以下で求めることができる。実際にはユーザごとの秘密鍵をDBに保存しておいて、都度必要な鍵を取り出して署名することになると思う。
fn http_sign(data: &[u8]) -> String { // load pem let private_key_str = r#"-----BEGIN RSA PRIVATE KEY----- ........ -----END RSA PRIVATE KEY-----"#; let private_key = PKey::private_key_from_pem(private_key_str.as_bytes()).unwrap(); // signing by rsa-sha256 let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap(); signer.set_rsa_padding(Padding::PKCS1).unwrap(); let mut len = signer.len().unwrap(); let mut buf = vec![0; len]; len = signer.sign_oneshot(&mut buf, data).unwrap(); buf.truncate(len); let signature = general_purpose::STANDARD.encode(buf); return signature; }
Actix Webに非同期でデータを渡す
Dependency InjectionとかでActix Webのサーバを起動するときにデータを渡しておき、リクエストハンドラの引数として受け取りたいケース。公式をはじめとして見つけやすいサンプルコードでは App:new().app_data() を使っているが、この方法はasync/awaitに対応していない。代わりに App::new().data_factory() を使う。
リクエストハンドラ側の受け取り方は一緒。
App::new() .data_factory(|| { async { <ここにawaitな処理を書く> } })
日本語配列でBluetoothマルチペアリングができるキーボード「Sunrise70」を作った
日本語配列で複数台のPCにBluetooth接続できるキーボードが欲しくなったが、既にある製品や自作キットだと矢印キーが横並びになってたり分割前提だったりしてどれも微妙に納得いかない感じだったのでイチから作った。スイッチは軽くて静かなのが気に入ったのでKailh BOX Silent Pink。
初めてのキーボードで70キーなので名前は「Sunrise70」。いつかend gameにたどり着いたらSunsetの名をつけたい。無刻印にしてMidnightとかも面白いかも。

特徴
- 使い慣れた日本語配列 。Majestouch 交換用キーキャップセットを使えば刻印と入力が一致するので安心。
- コントローラはスイッチサイエンスのISP1807搭載Microボードを採用。USB Type-Cと技適の通ったBLEが使えるマイコンの中では比較的安価で入手性が良い。ピン配置はPro Micro互換なので他のマイコンも一応使える。
- PCとの接続はBLEのみ、複数台の同時ペアリングが可能で接続先はホットキーで切り替え。電源はUSBから取るので電池切れの心配なし。
- スペースキーを2.25u x2にすることでスタビライザーの入手性を確保*1しつつ、好みに応じてShiftなどの割り当ても可能に。
- 基板とケースのサイズ、ネジの位置はサリチル酸さんのGL516互換。
- 光らない。
基板とスイッチプレート
KiCadで設計してJLCPCBに作ってもらった。ガーバーデータをアップロードして発注したところ長いキーのスイッチ穴とスタビライザー穴の間隔が狭すぎるという指摘が来たので、細い部分をなくして一つの大きな穴に変更した。基板の方は特に指摘事項もなく動作も一発OK。
こんな感じで直すところを指示してくれる。

シルクキレイだし穴開けも大丈夫そう
— あみだ (@_nibral) 2022年5月27日
サンキューJLCPCB pic.twitter.com/2YCmq2kZip
ケース
サリチル酸さんの公開しているデータをそのまま3Dプリンタで出力。Ender-3だと大きさの問題で1回では出力できないので、2分割して出力してから接着した。微妙に地面から浮いてる部分があったりエッジが面取りされてたりして印刷中にベッドから浮きやすいので、出力するときはラフトかブリムをつけた方が良さそう。


ファームウェア
QMKが使ってるマイコンに対応していないのでArduino IDEで自作。BLEまわりの処理はAdafruitが公開しているライブラリを参考にしつつ、スイッチが押されたら定義済みのHIDキーコードを送るようにすればOKだった。400行ないくらいだけどブログに貼ると長いのでGistに。
Firmware for Sunrise70 keyboard · GitHub
材料費
- 基板とスイッチプレート x5セット: $65.4(≒8,500円)
- 基板: $16.5
- スイッチプレート: $32.1 (穴が多くて追加料金)
- 送料: $15.8 (Standard Global Direct Line)
- PayPal手数料: $1
- ISP1807搭載Microボード x1: 3,300円
- Kailh BOX Slient Pink x70: 5,390円
- キーソケット x70: 1,309円
- Majestouch キーキャップセット x1セット: 2,691円
- 2uスタビライザー x4: 880円
- ダイオード(1N4148W) x80: 200円
これ以外にピンヘッダ・M2のネジ・ゴム足・長めのUSBケーブルなどを買っていることを考えると、1台作るのにおよそ16,000円かかったらしい。1/3はスイッチの値段なのでこんなもんかという感じ。
基板とスイッチプレートがあと4セット余ってるので、欲しい人がいたら原価でお譲りします。@_nibralまでDMください。
作ってみての感想
初めての自作キーボード無事完成した!
— あみだ (@_nibral) 2022年5月31日
自分で回路引いた基板を作ってもらって、ケースは家で3Dプリント、ファームウェアもArduino IDEで自作。ものづくりスキルの総合格闘技って感じでおもしろい。 pic.twitter.com/8xIQMvRp82
この記事はSunrise70で書きました。(これが書いてみたかった)
ECS + FargateでgRPCを動かす
gRPCでリクエストを受けるアプリをECS + Fargateで動かしつつ、ちゃんと負荷分散するために調べたことのメモ。
2021/2/22 追記
ALBがgRPCをサポートしたのでこの記事の内容は不要になった。
先に結論
リバースプロキシとしてenvoyを走らせて、ECS Service Discovery経由でつなぐのが良さそう。インターネットからの通信を受けたいならALBではなくNLBを使う。
検証用プログラム
gRPCでUUIDを返す。UUIDはサーバの起動時に1回だけ生成するので、UUIDの値を比較すれば負荷分散ができているかがわかる。
hello.proto
syntax = "proto3";
package hello;
option go_package = ".;main";
message HelloRequest {
}
message HelloReply {
string msg = 1;
}
service Hello {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
server.go
package main
import (
"context"
"github.com/google/uuid"
"google.golang.org/grpc"
"log"
"net"
)
type Service struct {
id string
}
func (service *Service) SayHello(ctx context.Context, message *HelloRequest) (*HelloReply, error) {
return &HelloReply{
Msg: "Hello from " + service.id,
}, nil
}
func main() {
port, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln(err)
}
server := grpc.NewServer()
helloService := &Service{
id: uuid.New().String(),
}
RegisterHelloServer(server, helloService)
err = server.Serve(port)
if err != nil {
log.Fatalln(err)
}
}
client.go
package main
import (
"context"
"flag"
"fmt"
"google.golang.org/grpc"
"log"
)
func main() {
flag.Parse()
conn, err := grpc.Dial(flag.Arg(0), grpc.WithInsecure())
if err != nil {
log.Fatalln(err)
}
defer conn.Close()
client := NewHelloClient(conn)
msg := &HelloRequest{}
res, err := client.SayHello(context.TODO(), msg)
if err != nil {
fmt.Printf("error::%#v \n", err)
}
fmt.Println(res.Msg)
}
ECSで動かしたいのでDockerfileも用意した。単にgo buildをするだけでなく、マルチステージビルドにしてdockerイメージを軽くするとかAlpine Linuxにglibcを入れるとかもやっている。
FROM golang:latest as builder
WORKDIR /go/src/grpc_sample
COPY server.go hello.proto ./
RUN apt update \
&& apt install -y protobuf-compiler \
&& go get google.golang.org/grpc \
&& go get github.com/golang/protobuf/protoc-gen-go
RUN protoc --go_out=plugins=grpc:. hello.proto
RUN go get -v ./...
RUN go build -o server server.go hello.pb.go
FROM alpine:latest
COPY --from=builder /go/src/grpc_sample/server server
RUN apk update && apk add libc6-compat
EXPOSE 50051
ビルドしたdockerイメージをECRにプッシュしたらECSの検証に移る。
ECSとロードバランサーとgRPC
ECSの負荷分散だとApplication Load Balancer(ALB)が使われることが多いが、ALBのターゲット側はHTTP/1.1にしか対応していないためgRPC(HTTP/2)では使えない。

ではどうするかというと、ALBの代わりにNetwork Load Balancer(NLB)を使う。NLBはレイヤー4(TCP)で処理を行うので、gRPCの通信も問題なく通過できる。これで一見良さそうに見えるが、設定を進めてみるといくつかの問題点が判明した。
- HTTP/2は1つのTCPコネクションを長く使うため、適切な負荷分散が行われない可能性がある(らしい)
- 特定のコンテナに負荷が偏る
- 今回使用したサンプルプログラムでは1回1回通信を張るので確認できず
- セキュリティグループの設定ができない
- コンテナにはクライアントの送信元IPがそのまま届くので 0.0.0.0/0 を許可するしかない
- SSLの終端ができない

セキュリティグループは頑張ってIPを設定する、SSLはいったん諦めるとして、負荷分散はちゃんとやりたいということでたどり着いた構成が以下。
NLBとサーバの間にEnvoyというプロキシを挟む。envoyからサーバへ通信するためには各サーバコンテナのIPを知る必要があるので、ECS Service Discoveryを使って server.grpc.local がコンテナのIPを返すようにしておく。

NLBのDNS名に対してクライアントからリクエストした様子。c9cf2091- で始まるUUIDと 62aa1e41- のUUIDが返ってきているのでちゃんと動いているはず。

envoy.yaml
envoy 1.16.0-dev-0b24c6で動作確認。envoy API v3。ネット上で見つかる記事だとv2の記述が多いので、調べるときは記事の日付を見た方が良い。
admin:
access_log_path: '/dev/null'
address:
socket_address:
address: 127.0.0.1
port_value: 9901
static_resources:
listeners:
- name: listner_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 5000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: backend
domains:
- '*'
routes:
- match:
prefix: '/'
route:
cluster: grpc_sample
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: '/dev/stdout'
http_filters:
- name: envoy.filters.http.router
clusters:
- name: grpc_sample
connect_timeout: 0.25s
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
http2_protocol_options: {}
health_checks:
- timeout: 5s
interval: 10s
unhealthy_threshold: 2
healthy_threshold: 2
tcp_health_check: {}
load_assignment:
cluster_name: grpc_sample
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: server.grpc.local
port_value: 50051
Amazon Auroraで"Unknown MySQL error"が出る
DjangoのチュートリアルをDockerで動かしていて、DBにAmazon Aurora (MySQL 5.7)を使おうとしたらERROR 2000 (HY000): Unknown MySQL errorが出た話。
先に結論
クエリキャッシュを無効にする (query_cache_type = 0) と解決する。
環境
- masOS Catalina 10.15.2
- docker desktop community 2.1.0.5 (40693)
問題のSQLクエリ
トップページ用にQuestionの一覧を取得する。
SELECT `polls_question`.`id`, `polls_question`.`question_text`, `polls_question`.`pub_date` FROM `polls_question` ORDER BY `polls_question`.`pub_date` DESC LIMIT 5;
調査
手元のMac + brewで入れたMySQL 8.0のクライアント → OK
% mysql -V mysql Ver 8.0.18 for osx10.15 on x86_64 (Homebrew) % % mysql -uadmin -p -h <Auroraのクラスターエンドポイント> -D mysite_db Enter password: mysql> SELECT `polls_question`.`id`, `polls_question`.`question_text`, `polls_question`.`pub_date` FROM `polls_question` ORDER BY `polls_question`.`pub_date` DESC LIMIT 5; +----+---------------+----------------------------+ | id | question_text | pub_date | +----+---------------+----------------------------+ | 1 | What's up? | 2020-01-16 07:45:11.757886 | +----+---------------+----------------------------+ 1 row in set (0.02 sec)
Alpine LinuxのDockerコンテナ + mariadb-client(10.4.10) → ERROR 2000 (HY000): Unknown MySQL error
% docker run -it --rm alpine:latest /bin/ash / # apk update / # apk add mariadb-client / # / # mysql -V mysql Ver 15.1 Distrib 10.4.10-MariaDB, for Linux (x86_64) using readline 5.1 / # / # mysql -uadmin -p -h <Auroraクラスターのエンドポイント> -D mysite_db Enter password: MySQL [mysite_db]> SELECT `polls_question`.`id`, `polls_question`.`question_text`, `polls_question`.`pub_date` FROM `polls_question` ORDER BY `polls_question`.`pub_date` DESC LIMIT 5; ERROR 2000 (HY000): Unknown MySQL error
クエリの一部(DESC LIMIT 5)を消すとエラーコードすら出なくなるが、何度か再実行すると正常な結果が返ってきたりして不安定。
MySQL [mysite_db]> SELECT `polls_question`.`id`, `polls_question`.`question_text`, `polls_question`.`pub_date` FROM `polls_question` ORDER BY `polls_question`.`pub_date`; ERROR: MySQL [mysite_db]> SELECT `polls_question`.`id`, `polls_question`.`question_text`, `polls_question`.`pub_date` FROM `polls_question` ORDER BY `polls_question`.`pub_date`; ERROR: MySQL [mysite_db]> SELECT `polls_question`.`id`, `polls_question`.`question_text`, `polls_question`.`pub_date` FROM `polls_question` ORDER BY `polls_question`.`pub_date`; +----+---------------+----------------------------+ | id | question_text | pub_date | +----+---------------+----------------------------+ | 1 | What's up? | 2020-01-16 07:45:11.757886 | +----+---------------+----------------------------+ 1 row in set (0.000 sec) MySQL [mysite_db]> MySQL [mysite_db]> SELECT `polls_question`.`id`, `polls_question`.`question_text`, `polls_question`.`pub_date` FROM `polls_question` ORDER BY `polls_question`.`pub_date` DESC LIMIT 5; +----+---------------+----------------------------+ | id | question_text | pub_date | +----+---------------+----------------------------+ | 1 | What's up? | 2020-01-16 07:45:11.757886 | +----+---------------+----------------------------+ 1 row in set (0.000 sec)
UbuntuのDockerコンテナ + mariadb-client(10.1.43) → ERROR 2027 (HY000): Malformed packet
% docker run -it --rm ubuntu:latest /bin/bash root@cc00c89acc69:/# apk update root@cc00c89acc69:/# apk upgrade root@cc00c89acc69:/# apk install mariadb-client root@cc00c89acc69:/# root@cc00c89acc69:/# mysql -V mysql Ver 15.1 Distrib 10.1.43-MariaDB, for debian-linux-gnu (x86_64) using readline 5.2 root@cc00c89acc69:/# root@cc00c89acc69:/# mysql -uadmin -p -h <Auroraクラスターのエンドポイント> -D mysite_db Enter password: MySQL [mysite_db]> SELECT `polls_question`.`id`, `polls_question`.`question_text`, `polls_question`.`pub_date` FROM `polls_question` ORDER BY `polls_question`.`pub_date` DESC LIMIT 5; ERROR 2027 (HY000): Malformed packet
解決
"Unknown MySQL error"では有益な情報にたどり着けなかったので"Malformed packet"で調べたところ、クエリキャッシュを切ったら直るという情報を発見。
Auroraのパラメータグループを変更したあと、Alpineのコンテナで確認。
/ # mysql -uadmin -p -h <Auroraクラスターのエンドポイント> -D mysite_db Enter password: MySQL [mysite_db]> SHOW VARIABLES LIKE '%query_cache_type%'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | query_cache_type | OFF | +------------------+-------+ 1 row in set (0.019 sec) MySQL [mysite_db]> MySQL [mysite_db]> SELECT `polls_question`.`id`, `polls_question`.`question_text`, `polls_question`.`pub_date` FROM `polls_question` ORDER BY `polls_question`.`pub_date` DESC LIMIT 5; +----+---------------+----------------------------+ | id | question_text | pub_date | +----+---------------+----------------------------+ | 1 | What's up? | 2020-01-16 07:45:11.757886 | +----+---------------+----------------------------+ 1 row in set (0.013 sec)
直った。少なからずパフォーマンスは落ちると思われる。
Auroraが変なレスポンスを返しているのか、MariaDBのクライアントがちゃんと解釈できてないのか、それ以外のところが悪いのか、よくわからない。
i3-9100Fで自宅PCを組んだ
メモリとSSDが安くなってたので勢いで。

構成
まず、いままで使っていたPCの構成がこちら。Netflixで映画を見たりETS2でヨーロッパを走り回る分には特に問題ないが、組んでから4年半が経過してるのでそろそろ入れ替え時。
| パーツ | 型番 |
|---|---|
| CPU | Intel Core i5-4460 |
| CPUクーラー | サイズ 虎徹 (SCKTT-1000) |
| メモリ | Patriot DDR3-1600 4GB x2 (PSD38G1600KH) |
| マザーボード | ASUS H97-PRO |
| GPU | MSI GTX 1060 AERO ITX 6G OC |
| SSD | OCZ Arc 100 240GB |
| HDD | WD Green 2TB (WD20EZRX) |
| ケース | Fractal Design Define R5 |
| 電源 | ENERMAX Platimax EPM750AWT |
で、新しいマシンの構成がこちら。CPU/メモリ/マザーの3点セットとSSD以外は流用した。
| パーツ | 型番 |
|---|---|
| CPU | Intel Core i3-9100F |
| CPUクーラー | サイズ 虎徹 (SCKTT-1000) |
| メモリ | CFD Crucial DDR4-2666 8GB x2 (W4U2666CM-8G) |
| マザーボード | ASRock B365M Pro4 |
| GPU | MSI GTX 1060 AERO ITX 6G OC |
| SSD | WD Blue SN500 NVMe M.2 SSD 500GB (WDS500G1B0C) |
| ケース | Fractal Design Define R5 |
| 電源 | ENERMAX Platimax EPM750AWT |
現行のi3は4コア4スレッドかつターボブーストも効く*1のでかつてのi5より性能が良い。グラボがあるので内蔵GPUなしのFモデルにしたのが今回のポイント。
組み立て
数年ぶりにケースをサイドパネルをオープン。Define R5は空気を取り入れるところにフィルタがついているので、ケース内のホコリは少なめ。

マザーボードから外してグリスをきれいにしたi5-4460。グリスの塗りなおしはしなかったので、表面の刻印を見たのは組んだ日とバラす日の2回だけ。

i3-9100F。ヒートスプレッダの形状が少し変わった。

マザーボードの説明書に従ってCPUクーラーを先に付けたら、M.2のヒートシンク取り付けがやりにくい。

マザーボードが一回り小さくなったのと、HDDがなくなったのとでケース内部がスッキリした。この写真を見ていて気付いたが、ケースファンが吸気x2 排気x1の構成なのでケース内が陽圧になっていてホコリが少なかったのかもしれない。

自作PCの鉄の掟「BIOS画面が出るまでサイドパネルを閉めない」を守ったので1発でブートした pic.twitter.com/82o8wYNO9j
— あみだ (@_nibral) 2019年12月24日
ベンチマーク
FF14ベンチは「非常に快適」判定。

CPUに100%負荷をかけても40℃ちょっとで安定している。(室温20℃)

お手頃価格とはいえさすがNVMeという速度。

まとめ
トータル3万円ちょっとでPCの世代交代ができてよかった。
*1:9100Fは最大4.2GHz
オフィスにCisco Meraki MR33を導入してみた
NTTから貸し出されるホームゲートウェイ(RS-500KI)の無線LANがどうにも不安定なので、ちゃんとしたアクセスポイントを導入した話。
Cisco Merakiとは
数年前にCiscoが買収したプロダクトで、無線LANのアクセスポイントやスイッチ、ファイヤーウォールなどを揃える。設定は全てクラウド上で一元管理されているのが特徴で、一般的なネットワーク機器のようにそれぞれの設定画面にログインして設定する必要がない。
Merakiシリーズはエンタープライズ向けなので一般消費者向けの小売りはされておらず、代理店経由で購入する必要がある。より小規模なネットワーク向けにMeraki Goというシリーズもあり、機能面では本家Merakiに及ばないものの、こちらはAmazonで買える。
無償検証用アクセスポイント
2019/10現在、Ciscoが提供するオンラインセミナー(ウェビナー)を受講すると検証用としてアクセスポイントがもらえるキャンペーンを展開している。
法人向けのためか多少のやりとりが必要なので、簡単に手順を。
まず、Merakiオンデマンドウェビナーのサイトにアクセスして「イントロダクション : クラウド管理型IT パワフルなITをよりシンプルに」の「視聴する」をクリック。
申し込みフォームが表示されるのですべて入力して「送信」。セミナー動画(1時間くらい)が再生できるようになるのでちゃんと見る。見終わると再度メールアドレスの入力フォームが表示されるので、メールアドレスを入力。

正常に受け付けられると、「アクセスポイントの送付先住所をMerakiの営業に送ってね」というメールが来る。

指定されたアドレスにアクセスポイントの送付を希望する旨と送付先をメール。ほどなくして相手方から電話があり、ネットワークの規模やアクセスポイントの使い方を聞かれたので素直に答える。問題なさそうだと判断されると(?)アクセスポイントが発送される。
今回はMR33というアクセスポイントと3年分のサブスクリプションがもらえることになった。定価で考えると機器代が99,800円、サブスクリプションが39,900円なのでかなり太っ腹。
発送されると「Your Meraki AP has shipped - xxxxxxxxx」というメールが来るので、大切に保存しておく。
外観
エンタープライズ向けらしくシンプルな箱。


MR33本体のほか、壁掛け用の台座と金具類が付属。

接続口はLANと電源のみ。底面が丸みを帯びていて端のほうは隙間ができるので、フラットでない普通のLANケーブルでも問題ない。

大きさはティッシュ箱と同じか一回り小さいくらい。構造的に縦置きはできず、平置きか壁掛けのどちらかを選ぶ必要がある。

小さいほうの箱にはACアダプターが入っていた。後で調べたところ、MR33はPoE給電が基本なのでACアダプターは別売とのこと。電話で「フレッツ光のホームゲートウェイ使ってます」という話をしたので、気を使ってくれたのかも。

初期設定
アクセスポイントのネットワーク設定
前述したようにMerakiのアクセスポイントはクラウド上で設定を行う。逆に言うとアクセスポイントがインターネットにつながっていないと何もできない。
DHCPでIPアドレスが配られているネットワークであればLANケーブルと電源をつなぐだけでよいが、静的IPを振る場合はいったんアクセスポイントに直接接続して設定を変える必要がある。詳細な手順は公式の設置ガイドを参照のこと。ステータスLEDが緑になればOK。
アクセスポイントを設置したら、以降はMerakiのWebサイトで設定を行う。
Meraki ダッシュボードアカウントの作成
MerakiのWebサイトにアクセスして右上の「Login」をクリック。

アカウントを作成するので「Create an account」。

リージョンは Asia を選択して「Next」。

続いてアカウントの情報を入力し「Create account」。

入力したメールアドレスに「Cisco Meraki Email Verification」というメールが届くので、本文中のURLを開いてメールアドレスの検証を行う。

デバイスとライセンスの登録
ダッシュボードを開くとWelcome的な画面が表示されるので「Register Meraki devices」を選択して「Next」。

ネットワーク名とネットワークに参加させるデバイスの選択画面。Merakiにおけるネットワーク機器の管理は 組織 → ネットワーク → デバイス という階層構造になっている。
のようなイメージ(たぶん)。また、ネットワークに所属していないデバイスは インベントリ にプールされる。

インベントリにデバイスを追加する際は、購入した際のオーダー番号か製品記載のシリアルナンバーを使う。今回は1台だけなのでシリアルナンバーを入力して「Add devices」した。

ネットワーク名を入力し、追加したMR33にチェックを入れたら「Create network」。

正常にネットワークが作成されるとネットワークに所属しているアクセスポイントの一覧画面が表示されるが、この段階ではデバイスを登録しただけでサブスクリプションが有効になっていない。
サブスクリプションを有効にするには「Your Meraki AP has shipped - xxxxxxxxx」のメールに記載のURLをクリックする。サブスクリプションが有効になると、左側のメニュー Organization → License info のLicense statusが Ok になる。

これで無線LANアクセスポイント1台 x 3年のライセンスが有効になった。
MR33の面白いところ
電波状態モニターの表示が細かい
各チャンネルの混雑具合と時間軸での変化が色分けで表示される。ほぼリアルタイムで更新されるので、チャンネルを割り当てるときの参考にするもよし、スピードテストを走らせて色が赤くなるのを楽しむのもよし。

NATとファイアウォールがついてる
IP割り当てをNAT modeにすると、アクセスポイントがDHCPサーバとして動作して配下の端末に10.0.0.0/8のIPアドレスを配るようになり、無線LANクライアント間での通信が行えなくなる。

ファイアウォールでは、IPアドレスやポートを指定して通信を拒否できる。デフォルトでは有線LAN側端末へのアクセスを拒否する設定なので、NASにアクセスする場合などは許可する必要がある。

2つの設定をうまく組み合わせるとゲスト用のネットワークのセキュリティを高められる。
ゲスト用SSIDにスプラッシュページが出せる
インターネットに出る前に「ご来社ありがとうございます」的なページを出したり、認証を求めることができる。試してはいないが、ページの種類をBillingにして課金制にすることもできるようだ。

導入して1週間、今のところは安定して動作しているしネットワークの管理もやりやすいと思う。家庭用の無線LANルータが3,000円ちょっとで買える時代ではあるが、エンタープライズ向けはいろいろ設定できて楽しい。
AWS SAMでDefaultAuthorizerとCORSが共存できるようになった
半年ほど前に以下のような記事を書いたが、その後のアップデートでDefaultAuthorizerとCORSが問題なく共存できるようになった。
関係しそうなところ
- AWS::Serverless::ApiのAuthプロパティに「AddDefaultAuthorizerToCorsPreflight」が追加され、CORSのプリフライトリクエストにDefaultAuthorizerを適用するかどうかを指定できるようになった (AWS SAM v1.13~)
- 以前テストをパスできなくて取り下げられていたもの
- 「sam local start-api」がCORSに対応した (AWS SAM CLI v0.21.0~)
- Authorizerには直接関係しないが、ローカルでプリフライトリクエストの動作確認ができるようになった
設定例
以下のような template.yml を作成すると、
- GET /
- CognitoAuthorizer
- GET /public
- Authorizerなし
- OPTIONS / および OPTIONS /public
- Authorizerなし (プリフライトリクエスト成功)
という動作が実現できる。AWS SAM CLI v0.22.0で確認。
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
Auth:
DefaultAuthorizer: CognitoAuthorizer
AddDefaultAuthorizerToCorsPreflight: false
Authorizers:
CognitoAuthorizer:
UserPoolArn: <Cognito UserPool ARN>
Cors:
AllowOrigin: "'*'"
MyFunction1:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
CodeUri: src/
Events:
GetIndex:
Type: Api
Properties:
Path: /
Method: get
MyPublicFunction1:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
CodeUri: src/
Events:
GetPublic:
Type: Api
Properties:
Path: /public
Method: get
Auth:
Authorizer: 'NONE'
もともとやりたかったことができるようになったので満足。
