Container Runtime Meetup #1 - connpass に参加して NOTIFY_SOCKET
について調べたことを話してきました。
この記事ではその内容の書き起こしと、その場で行われた会話についてのメモについて書きます。
@udzuraさんにそそのかされたことがきっかけです。
@yu_suke1994 情報です https://t.co/U5zsvUlz30
— Uchio KONDO 🔫 (@udzura) August 27, 2019
あまりよくないExecuteをすると、普段はRails APIを運用していてRubyしか書いておらず、Goも数年前にCLI toolを作った程度で、GoやLinuxのコンテナ回りに詳しいという訳ではありません。
connpassのイベント概要に
少人数の輪読形式です。「参加枠1」の方々にはあらかじめ「runc run」周辺のコードをざっと読んできてもらい、当日、それに関連するトピックをそれぞれ発表して頂きます。「聴講のみ」の方々は、発表の必要はありません。
とあったので、その時点での最新リリースである v1.0.0-rc8 を対象に読むことにしました。
https://github.com/opencontainers/runc/releases/tag/v1.0.0-rc8
runc run
が実行されたとき、呼び出される実体は run.go だろうとアタリをつけ、周辺を読んでいきます。
status, err := startContainer(context, spec, CT_ACT_RUN, nil)
if err == nil {
// exit with the container's exit status so any external supervisor is
// notified of the exit with the correct exit status.
os.Exit(status)
}
https://github.com/opencontainers/runc/blob/v1.0.0-rc8/run.go#L76
まずここで startContainer
によりContainerがstartするものと思われます。中を見ていきます。
func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
id := context.Args().First()
if id == "" {
return -1, errEmptyID
}
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
if notifySocket != nil {
notifySocket.setupSpec(context, spec)
}
https://github.com/opencontainers/runc/blob/v1.0.0-rc8/utils_linux.go#L405-L414
startContainer
内部、 411行目にて os.Getenv("NOTIFY_SOCKET")
としている部分があります。この環境変数は何でしょう?気になったので、ここを掘っていきました。
この時点で僕の NOTIFY_SOCKET
に対する認識は、
「環境変数がある状態で起動させると色々な通知が飛んでくるのだろうか?」
くらいのものでした。
では、 newNotifySocket
で何が行われているかを見ていきます。
func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *notifySocket {
if notifySocketHost == "" {
return nil
}
root := filepath.Join(context.GlobalString("root"), id)
path := filepath.Join(root, "notify.sock")
notifySocket := ¬ifySocket{
socket: nil,
host: notifySocketHost,
socketPath: path,
}
return notifySocket
}
https://github.com/opencontainers/runc/blob/v1.0.0-rc8/notify_socket.go#L23-L38
この関数の返り値として、 notifySocket
構造体のインスタンス(のポインタ?)が得られます。
ちなみに notifySocket
構造体はこのように定義されています。
type notifySocket struct {
socket *net.UnixConn
host string
socketPath string
}
https://github.com/opencontainers/runc/blob/v1.0.0-rc8/notify_socket.go#L17-L21
ところで newNotifySocket
関数の引数として渡される context *cli.Context
は何でしょうか。
これはruncが採用しているCLIツール用パッケージ https://github.com/urfave/cli で定義されている Context 構造体を指しています
https://godoc.org/github.com/urfave/cli#Context
startContainer
では、 newNotifySocket
を呼んだ直後に、返ってきた notifySocket
に対して setupSpec
を呼んでいます。これも見ていきます。
// If systemd is supporting sd_notify protocol, this function will add support
// for sd_notify protocol from within the container.
func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) {
mount := specs.Mount{Destination: s.host, Source: s.socketPath, Options: []string{"bind"}}
spec.Mounts = append(spec.Mounts, mount)
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", s.host))
}
https://github.com/opencontainers/runc/blob/v1.0.0-rc8/notify_socket.go#L44-L50
この関数のコメントにもあるように、systemdが何か関係していそうだということがわかりました。
ここでの処理は runtime-spec を読むとわかりそうです。とりあえず、Process.Env
に対して NOTIFY_SOCKET
環境変数を追加しているようです。
startContainer
に戻ると、 createContainer
を呼んだあとに notifySocket.setupSocket()
を呼んでいます。これを見ていきます。
func (s *notifySocket) setupSocket() error {
addr := net.UnixAddr{
Name: s.socketPath,
Net: "unixgram",
}
socket, err := net.ListenUnixgram("unixgram", &addr)
if err != nil {
return err
}
s.socket = socket
return nil
}
https://github.com/opencontainers/runc/blob/v1.0.0-rc8/notify_socket.go#L52-L65
net.UnixAddr
とは何でしょうか。これの実体は https://golang.org/pkg/net/#UnixAddr にあります。
type UnixAddr struct {
Name string
Net string
}
ここでの unixgram
は datagram socketを指すようで、UDPのような送りっぱなしのプロトコルのようです。
( https://github.com/golang/go/blob/master/src/net/unixsock_posix.go#L16-L27 の syscall.SOCK_DGRAM
を参照 )
net.ListenUnixgram
によってconnectionを張り、それを notifySocket.socket
に格納したものを、 runner
構造体の notifySocket
フィールドに入れています。
type runner struct {
init bool
enableSubreaper bool
shouldDestroy bool
detach bool
listenFDs []*os.File
preserveFDs int
pidFile string
consoleSocket string
container libcontainer.Container
action CtAct
notifySocket *notifySocket
criuOpts *libcontainer.CriuOpts
}
https://github.com/opencontainers/runc/blob/v1.0.0-rc8/utils_linux.go#L254-L267
この後に、 notifySocket
に対して行われている操作は、runner.run
内部で以下のコードを呼んでいる部分があります。
// Setting up IO is a two stage process. We need to modify process to deal
// with detaching containers, and then we get a tty after the container has
// started.
handler := newSignalHandler(r.enableSubreaper, r.notifySocket)
https://github.com/opencontainers/runc/blob/v1.0.0-rc8/utils_linux.go#L305-L308
一旦ここまでをまとめると、
NOTIFY_SOCKET
という環境変数をもとにsoket通信をしている?unixgram
によって通信するものといったところでしょうか。
何やらsystemdが関係していそうなことはわかっているので、単純に NOTIFY_SOCKET
でググってみると、いくつか記事が見付かりました。
freedesktop.orgでは
These functions send a single datagram with the state string as payload to the AFUNIX socket referenced in the \$NOTIFYSOCKET environment variable. If the first character of \$NOTIFY_SOCKET is “@”, the string is understood as Linux abstract namespace socket.
https://www.freedesktop.org/software/systemd/man/sd_notify.html#Notes
「sd_notifyの通信方法 - Qiita」では
systemdのマネージャ(デーモンプロセス)は、起動プロセスの最後の方でsdnotifyという関数を用いて、起動が完了したことをsystemd本体(PID=1)に通知する。(注:sdnotifyは実際にはもっと汎用的なステータス通知に使える。)
https://qiita.com/ozaki-r/items/ced43d5e32af67c7ae04
ざっくりとした理解でいくと、プロセスが起動した、などのステータスの通知に使用されているんだろうということがわかりました。
ここで、「ではDockerの場合はどうなのだろう」と思い、 https://github.com/docker/cli 内をgrepしましたが、見当りません。いや、Dockerはmobyに移行したのでした。 案の定、 https://github.com/moby/moby 内をgrepすると見付かりました。
$ git grep NOTIFY_SOCKET
libcontainerd/supervisor/remote_daemon.go:200: // clear the NOTIFY_SOCKET from the env when starting containerd
libcontainerd/supervisor/remote_daemon.go:203: if !strings.HasPrefix(e, "NOTIFY_SOCKET") {
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:49:// If `unsetEnvironment` is true, the environment variable `NOTIFY_SOCKET`
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:53:// (false, nil) - notification not supported (i.e. NOTIFY_SOCKET is unset)
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:54:// (false, err) - notification supported, but failure happened (e.g. error connecting to NOTIFY_SOCKET or while sending data)
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:58: Name: os.Getenv("NOTIFY_SOCKET"),
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:62: // NOTIFY_SOCKET not set
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:68: if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil {
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:74: // Error connecting to NOTIFY_SOCKET
https://github.com/moby/moby/search?q=NOTIFY_SOCKET&unscoped_q=NOTIFY_SOCKET
ということは、Dockerを使っているだけでそのようなsocketが作成されているのではないでしょうか。調べてみましょう。
Docker daemonが動作しているマシン上で、systemdが関係していそうなunixドメインソケットの数を出してみました。
$ ss --family=unix | grep systemd | wc -l
110
けっこうな数ありますね。grepする単語を変えてみます。
$ ss --family=unix | grep container
u_str ESTAB 0 0 /var/snap/microk8s/common/run/containerd.sock 21833469 * 21836828
u_str ESTAB 0 0 /var/snap/microk8s/common/run/containerd.sock 21836830 * 21836829
u_str ESTAB 0 0 /var/run/docker/containerd/containerd.sock 23108 * 23106
u_str ESTAB 0 0 /var/run/docker/containerd/containerd.sock 23114 * 23113
u_str ESTAB 0 0 /var/run/docker/containerd/containerd.sock 23110 * 25748
それらしきものが存在しています。
もっと見ていきましょう。dockerdのpidを調べます。
$ ps aux --forest # 抜粋
root 601 /usr/bin/dockerd -H fd://
root 769 \_ containerd --config /var/run/docker/containerd/containerd.toml --log-level info
root 1213 \_ containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/61f9489e17355a4f00594feb5c
root 1230 \_ bash
pid 601
の環境変数を見てみます。
$ sudo cat /proc/601/environ # 改行を入れています
LANG=ja_JP.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/var/lib/snapd/snap/bin
NOTIFY_SOCKET=/run/systemd/notify
LISTEN_PID=601
LISTEN_FDS=1
LISTEN_FDNAMES=docker.socket
INVOCATION_ID=e65738cc4b8f461e968d23c6740a557e
JOURNAL_STREAM=9:22835
確かに NOTIFY_SOCKET
がありますね。
NOTIFY_SOCKET
でsystemdとやりとりしているようだdockerd
には NOTIFY_SOCKET
が与えられていることが確認できたrunc run
を実行したときにもこれは与えられるのか?ここまでがMeetupでの発表内容になります。
ここからは、当日の発表のあと、その場で行われた議論の簡単な書き起しになります。
NOTIFY_SOCKET
が与えられるのは、systemd経由で起動した場合だけで、bashなどから直接 $ runc run
など実行してもこの環境変数が自動的に作成されるということはない。
NOTIFY_SOCKET
を活用できるのであれば、docker-composeにおける pg_isready
などのhackが不要になるんじゃないか?
return
しているだけなので改善の余地はありそう
sd_notify
を使用して自身の状態を通知しないといけないからそれもハードルになるのではないか
Type=notify
がそれらしい2019年5月1日から、個人事業主として株式会社バンクで1日8時間の週4日間で稼動していました。
「週4日労働にしたかった」 ためです。好きなようにコードを書いたり、映画など娯楽に興じたり、心身を養ったりする時間のために、もう1日休日が欲しいという気持ちは2019年になってから心のどこかにずっと引っかかっていました。
ただ、フリーランスになるとは言ってもバンクを離れたくはありませんでした。当時から計画されていた新サービスの実装、まだまだ綺麗にし足りないコード、インフラ構成のキャッチアップ……やりたいことは山ほどありました。
なのでまずは人事の方に相談し、こちらの意向として
ということを伝えました。
結果としてですが、フリーランスとして契約をし直すということになりました。
最初に人事の方に「フリーランスを検討している」という意向を伝えたのが2月28日、面談を行ったのが3月8日、代表の光本さんと話したのが3月15日と19日、そこから総務の方と話し、有休の消化等の兼ね合いで、5月1日からフリーランスとして勤務する、という時系列になっています。
その他、詳細や発表資料などについては https://unasuke.com/activity などにまとめてあります。また各種ポートフォリオサービスへのURLは以下になります。
バンクでの契約が一区切りしたタイミングというのもあり、このタイミングで公開しました。一区切りしたとはいえ、契約更新のタイミングであったというだけで、まだバンクで引き続きお仕事をしています。
( ※ この日記は、フリーランスになった5月頃から、最初の契約更新のタイミングである11月1日に公開するつもりで下書きを作成していましたが、解散発表のタイミングで公開することにしました。 )
これからフリーランスとして仕事をしていくなかで、ずっとバンクだけと関わっていく訳ではないので、 興味のある方は声をかけていただけるとありがたいです。
(解散に伴なって色々あり、来年までは埋まっているかもしれません。状況を見つつ動きたいと思います。)
ただ、将来的に実家のある福井に戻りたいという展望があり、徐々に仕事をリモートで行えるようにしていきたいと思っているので、フルリモートが可能であると(より)嬉しいです。
また「交渉の過程」でも触れたように、「週4日勤務」という条件を満たせるのであれば正社員としてのオファーも嬉しいです。
うなすけです。
FANBOXで支援者数が50名以上いる方を対象に特別価格で提供していたOKAMURAのオフィスチェアですが、この度なんと!対象を「支援者数10名以上」に緩和して再開しました 🎉🎉
— pixivFANBOX公式 (@pixivFANBOX) July 1, 2019
今まで欲しくても手が届かなかった方はこの機会にぜひチェックしてみてください! #クリエイター優待https://t.co/qaektxPHCB
#pixivFANBOX やっててよかった!!
— うなすけ (@yu_suke1994) July 1, 2019
毎月支援していただいている皆様、本当にありがとうございます。
前職、spice life時代には、好きな椅子を何でも一つ購入できるという制度があり、覚えているだけでも社内にアーロンチェア、エンボディチェア、バロン、コンテッサがありました。
それらオフィスチェアのなかでも、僕の身体にはバロンがしっくりくるようで、いずれは自宅にもバロンを……という目標をその頃から立てていました。が、いわゆる高級オフィスチェアなのでなかなか手が出ずにいました。
そこにpixivFANBOXでのクリエイター優待としてオカムラのオフィスチェアを購入できるようになり、ついにこの時が!となったものの、公開当初は50人以上の支援がないと利用できず、僕は対象外で悔しい思いをしたものです。
【お知らせ】
— pixivFANBOX公式 (@pixivFANBOX) April 9, 2019
FANBOXの支援者を集めているクリエイターを対象に!
なんと…!
OKAMURAのオフィスチェアを特別価格で購入できるようになりました〜 🎉
日頃の創作活動により集中できるように、普段より良い椅子を使ってみませんか??詳細はこちら↓↓ #クリエイター優待https://t.co/qaekty7iu9
そして、今自宅で使っている椅子は、どこで買ったのかも覚えていない1、上京したとき購入したものです。座面はへたってほぼ板、というかネジか何かが座面に飛び出している、軋みがうるさい、腰が痛くなる……4年ちょっと使っている安物なので、こんなものでしょう。しかしストレスが溜まっていきます。
(部屋が汚ない)
そんなタイミングで、冒頭にあるようにOKAMURAのオフィスチェア優待が10人以上の支援者からでも申し込み可能と知ったときの嬉しさったらありません。
8月1日に申し込んで、8月9日に自宅に届きました。
(部屋が汚ない)
最高の座り心地ですね。やはり良い椅子は良いです、ありがとうpixivFANBOX。ありがとう支援者の皆さん。重ねて御礼申し上げます。
さて、注文時のオプションに「椅子引き取り」というものがあり、使用中の椅子を引き取ってくれるとのことで、これを有効にして注文しました。
しかし配送の方には「そんなのは聞いていない」と言われてしまい、アレ~となりました。注文確認メールにはしっかり「椅子引き取り」のオプションが有効になった状態になっている2のですが……まあ配送の方を詰めても何もいいことはない3ので、そのまま帰っていただき、こちらで処分することにしました。
我が家には2週間に1回くらいのペースで廃品回収のチラシが来るのですが、ある程度の見積りは書いてあるものの、やはり呼んでから法外に請求されるのは怖いので、行政の廃品回収をお願いすることにしました。行政だと値段が事前にわかるので最高、安心ですね。
そのかわり、狭い部屋に二脚の椅子でより狭いという状況がしばらく続きそうです。
おそらく https://www.nitori-net.jp/store/ja/ec/Chair/WorkChair/6620437-6620439s の旧モデル ↩
郵送で届いた領収書にも「椅子引き取り」の文字がしっかり記載されていました……何? ↩
でも服にOKAMURAのロゴが入ってたし社員かもしれなかったのでもうすこし追求してもよかったかもしれない? ↩
OpenAPIによる定義から実装を生成したいニーズはあり、その方法は存在します。
「スキーマファースト開発」という言葉もあるように、一般的にはREST API schemaを定義してから実装にとりかかります。
しかし、様々な事情で「既存のREST API実装に対してOpenAPI schemaを記述したい」というニーズがあります。
例えばRailsの config/routes.rb
の内容から OpenAPI の paths
に相当するYAMLやJSONを出力するようなgemがあると助かるのですが、rubygems.org を “openapi” で検索してもそれらしいgemは見当りませんでした。
なので、そういうgemをつくりました。
※ 以下、特記していない場合には Rails v5.2.3 時点のコードになります。
実装にあたって、まず参考にしたのがお馴染み bin/rails routes
の処理になります。このとき何が行われているのでしょうか。
bin/rails routes
で実行されるコードは以下です。
# frozen_string_literal: true
require "optparse"
desc "Print out all defined routes in match order, with names. Target specific controller with -c option, or grep routes using -g option"
task routes: :environment do
all_routes = Rails.application.routes.routes
require "action_dispatch/routing/inspector"
inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
routes_filter = nil
OptionParser.new do |opts|
opts.banner = "Usage: rails routes [options]"
Rake.application.standard_rake_options.each { |args| opts.on(*args) }
opts.on("-c CONTROLLER") do |controller|
routes_filter = { controller: controller }
end
opts.on("-g PATTERN") do |pattern|
routes_filter = pattern
end
end.parse!(ARGV.reject { |x| x == "routes" })
puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, routes_filter)
exit 0 # ensure extra arguments aren't interpreted as Rake tasks
end
https://github.com/rails/rails/blob/v5.2.3/railties/lib/rails/tasks/routes.rake
ここでの本質は
inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, routes_filter)
の2行でしょう。
では、 ActionDispatch::Routing::RoutesInspector
は何でしょう。
##
# This class is just used for displaying route information when someone
# executes `rails routes` or looks at the RoutingError page.
# People should not use this class.
class RoutesInspector # :nodoc:
def initialize(routes)
@engines = {}
@routes = routes
end
...sinp
はい、private API ですね。
この RoutesInspector に適切なFormatterを渡して、routesの結果を整形すればよさそうです。
では ActionDispatch::Routing::ConsoleFormatter
を見てみます。
class ConsoleFormatter
def initialize
@buffer = []
end
def result
@buffer.join("\n")
end
def section_title(title)
@buffer << "\n#{title}:"
end
def section(routes)
@buffer << draw_section(routes)
end
def header(routes)
@buffer << draw_header(routes)
end
def no_routes(routes)
@buffer <<
if routes.none?
<<-MESSAGE.strip_heredoc
You don't have any routes defined!
Please add some routes in config/routes.rb.
MESSAGE
else
"No routes were found for this controller"
end
@buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
end
private
# ...snip
RoutesInspectorと同様に(明記されていませんが)これもprivate APIでしょう。
少し下に /rails/info/routes
で使用される HtmlTableFormatter
も定義されており、それと見比べると、 result
、 section_title
、 section
、 header
、 no_routes
を定義した独自のFormatterを作成すればよさそうに見えます。
さて、 OpenAPI v3 では、以下のような記述をするよう、仕様で定義されています。
openapi: 3.0.2
info:
title: example
description: OpenAPI example
version: 0.1.0
servers:
- url: http://api.example.com/v1
description: example server
paths:
/users:
get:
summary: get users
description: Return all user list
responses:
'200':
description: users json
content:
application/json:
schema:
type: array
items:
type: string
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md
これらの定義のうち、 paths
以下のいくつかについては、 config/routes.rb
から自動生成できそうです。
なので、以下のようなFormatterを作成すると、それらしい定義を生成できます。
module ActionDispatch
module Routing
class OpenAPI3Formatter
def initialize()
@view = nil
@buffer = []
@openapi_structute = {
'openapi' => '3.0.0',
'info' => {
'title' => '',
'description' => '',
'version' => '0.1.0'
},
'paths' => {}
}
end
def section_title(title)
end
def section(routes)
routes.filter do |r|
!r[:verb].empty?
end.each do |r|
@openapi_structute['paths'][r[:path]] ||= {}
@openapi_structute['paths'][r[:path]][r[:verb].downcase] = {}
@openapi_structute['paths'][r[:path]][r[:verb].downcase] = {
'summary' => r[:name],
'description' => r[:reqs],
'responses' => nil
}
end
end
def header(routes)
end
def no_routes(*)
end
def result
YAML.dump @openapi_structute
end
end
end
end
そして、それをgemにしたのがこれです。
https://github.com/unasuke/openapi3_definition_generator-rails
使いかたはREADMEにあるとおり、Gemfileに追記して bundle installした上で、 $ bin/rails openapi3_definition:generate_yaml
雑に表参道.rbで話したのが、これです。
上で述べたように、内部でPrivate APIにしっかり依存しているので、いつ動かなくなるかは保障できず、そのため Rails v5.2.3 以上 v6 未満でしかインストールできないようになっています。Pull Requestは大歓迎です。
今後、実装するとしたら
くらいと、あとは表参道.rbでもアドバイスを頂いたように、Rails本体への機能追加も考えています。
オタク!退職エントリより
— 衰咲ふち💬アーバンキャット (@otoroesaki) July 2, 2019
異常独身男性エントリ書いて!
「異常独身男性注意マスキングテープ (マスキングテープ - テープ幅 15mm)」を 限界集落 で購入しました! https://t.co/7Z8WodNZtK #booth_pm
— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) June 17, 2019
異常独身男性にマスキングテープ貼り付けるいたずらをやっていこうと思います
— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) June 17, 2019
左隣に座ってる同僚には絶対貼りたい
— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) June 17, 2019
ふちちゃん、一枚セールスしたわよ! pic.twitter.com/sgvFb7L114
— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) June 17, 2019
アアアアアーーーアアアアアーアーアーアーアーアーアアアーーーーーアアーア
— うなすけ (@yu_suke1994) June 17, 2019
異常独身男性:背面有〼 Tシャツ ブラック Mを買ったよ。 https://t.co/kWBTbhCXKp #suzurijp @suzurijpさんから
— うなすけ (@yu_suke1994) July 5, 2019
異常独身男性 pic.twitter.com/WLL3xaBUUg
— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) July 8, 2019
異常独身男性 pic.twitter.com/GYMkDIDBO4
— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) July 8, 2019
うなすけくんには異常独身男性界を背負って立ってほしい
— 衰咲ふち💬アーバンキャット (@otoroesaki) June 23, 2019
今生まれ出づる異常独身男性ニューエイジ
— 衰咲ふち💬アーバンキャット (@otoroesaki) June 28, 2019
うなすけ
衰咲ふちの限界集落で異常独身男性シリーズが発売されたわよ! #衰咲
— 衰咲ふち💬アーバンキャット (@otoroesaki) June 15, 2019
異常独身男性:背面有〼Tシャツ https://t.co/jk7jWiaztm
異常独身男性ステッカー(COLOR) | 限界集落 https://t.co/MIDepETLUe
異常独身男性マスキングテープ | 限界集落 https://t.co/wEfJrhzywj pic.twitter.com/7csRAsAb5E
カラー色々!選べる異常独身男性!
— 衰咲ふち💬アーバンキャット (@otoroesaki) June 15, 2019
ブルーカラーもホワイトカラーも一緒に仲良く異常独身男性着ようね!(皮肉) pic.twitter.com/zkiVFOaoZb
2018年10月頃のgroup buyから積みっぱなしになっていたFortitude60を、重い腰を上げてようやく組み立てました。
発端は、友人の @nikuzuki_29 が職場で布教された結果「自作キーボードを組んでみたい」という気持ちになり、では僕も、とスイッチ、キーキャップを揃えて組み立てることにした、という流れになります。
肉好きが初心者であることから、一日中遊舎工房のフリースペースを使うつもりで早起きして秋葉原に行き、組み立てを開始しました。(事実、閉店まで居ました)
僕も久々の半田付けであることから、まずはmeishiを各々組み立てて慣れておく作戦をとったのが良かったです。
遊舎工房で1日かけて組み立てたのですが、その場でArch LinuxからFirmwareを焼こうとするとエラーになってしまいました。レンタルスペースで焦っていたのもあったのでその場での解決は諦めましたが、帰宅後プルリクを出しmergeされました。
✌️
— うなすけ (@yusuke1994) June 16, 2019
Install avrdude in Arch or Manjaro Linux by unasuke · Pull Request #6132 · qmk/qmkfirmware https://t.co/qhmxedwFbM
CとHを取り違えたので、若干ヒビが入ってしまいました。
右手側が全く反応しません。テスターを買ったので、追い追い時間を見つけて調査と修正、あるいは再度購入をしていこうと思っています。
適当なTai-HaoのキーキャップとBlank keycapsを購入したのですが、キーごとに高さが違う構成なので、PimpMyKeyboardで改めて好みのを買おうと思っています。またTRRS、USBケーブルも味気ないので買おうかなと考えており、沼感が出てきました。
v1.10.3からのdiffはこちらです。
https://github.com/itamae-kitchen/itamae/compare/v1.10.3…v1.10.4
RUBYOPT=-w
を指定したときに出る警告を修正するもの。
これは v1.10.3 にmergeされている Add integration test with itamae local
command で、test filesを列挙する正規表現が誤っていた問題を修正するもの。 これで 0 examples
→ 139 examples
になりました。よかったですね。
このあたりはpull req authorのpockeさんがブログに書いてくれていますね。メンテナとして気付けなかった僕達のミスでもあります。ありがとうございます。
Itamaeのテストを壊してしまっていた話 - pockestrap
itamae docker
‘s created message by pocke · Pull Request #288 · itamae-kitchen/itamaeitamae docker
コマンドに --tag
オプションを指定した場合、成功したときのmessageにtagも表示するようにしたもの。
itamae docker
コマンドのhelp messageに --tag
オプションの説明を足したもの。
徐々にテンションが上っていく感じの曲順になるようにやってみましたが、総評としてはパリピでしたね。精進します。
イベント自体は3月末にあったので公開がとても遅れてしまいました。
今回、高専DJ部の開催スケジュール的に平成最後の開催となるので、IMAP++とのコラボを持ちかけたらなんと実現してしまいました。 Webサイトもmazcoさんに特別にデザインしてもらったりと、本当に記念となる回にできたかなと思います。
また、 #unasukefmのために購入したZOOM H4n Proで録音したものを内々に共有したりもしました。冒頭のmixcloudはそこから切り出したものになります。
4/15からの1週間、福岡のConference Weekで何をしてきたのかという日記です。
カンファレンス開始前の15日には、GMOペパボで @udzura さんを相手に #unasukefm の収録を行いました。
Haconiwaについて聴いてる #unasukefm pic.twitter.com/gp67dELrVQ
— カルパス (@yoshi_hirano) 2019年4月15日
最新エピソードを配信しました。RubyKaigi 2019のLocal Organizerである @udzura さんにRubyKaigiの舞台裏、福岡移住の話、Haconiwaについて聴いています。ぜひ聴いて下さい - https://t.co/9JjFL6dOBv #4 - #kaigieffect (@udzura) by Railsdm Podcast #rubykaigi2019https://t.co/kjOtdiYJUO
— Railsdm (@railsdm) 2019年4月22日
ハッシュタグ #unasukefmの様子を見るに、皆さんに楽しんでいただけたようでなによりです。
実はudzuraさんを相手に収録をしたいというのは、Railsdm PodcastがunasukefmとしてSeason 2を開始したときからほとんど決まっていたようなものなのです。達成でき、HaconiwaやCloudNativeの話ができてよかったです。
そして1番目のカンファレンス、CloudNative Days Fukuoka 2019に参加しました。
ここではKubernetesだけに留まらない、CloudNativeな技術に関する様々なトークを聞くことができました。特に僕が今課題を感じている点である、秘匿情報の管理についてVaultを使うケースを知れたのはよかったです。
懇親会でも、福岡でしか会えない方々をはじめ、Rubyコミュニティとはまた違うCloudNativeなコミュニティの方々とお話しすることができました。
次に、1日挟んで迎えたRubyKaigi 2019です。挟んだとは言ったものの、僕は今回(も)helperとしてお手伝いする立場なので、17日も1日中設営作業をしていました。
https://photos.app.goo.gl/svaNw2jvBtzusJ259 ← 様子を軽くまとめました。
今回はネットワーク斑としてケーブルの敷設、同時通訳レシーバーの受け渡し、スピーカーへの同時通訳打ち合わせ案内などを主に担当しました。英語が咄嗟に出てこない&聞きとれないのがまだまだ課題として感じられる3日間でした。
なるほどね pic.twitter.com/jqoAAe4vng
— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) 2019年4月18日
2019年 pic.twitter.com/CCBO4FeLDX
— Yuki AKAMATSU (@ukstudio) 2019年4月21日
今になっても、「ああ、良かったなぁ」というふんわりとした感想しか出てきません。
皆さんは https://github.com/notifications/participating を普段どれだけ見ていますか。僕はこの機能をよく使うのですが、集中していたりすると見に行くのを忘れて、コメントされているのに返事をしそこねてしまったまま長い時間経ってしまうということがしばしばありました。
こっちからアクセスしに行かなくても、「未読がこれだけあるよ」と教えてほしいわけです。
とはいっても、コメントされて数秒で通知が来る、という即時性は求めていません。なぜなら、例えば社のリポジトリに関することであれば、GitHubのコメントやmergeをSlackに流しているからです。コメントの応酬はそっちで見れます。
ユースケースとしては、「出社してまず見る」とか、「集中してて気づかなかったけどあのpull reqにコメントついてるっぽい」だとか、そういうのを求めていました。
https://github.com/unasuke/github-notification-reminder
これをheroku schedulerで定期的に叩くことによって、このように通知させています。
Deploy to herokuボタンを作ったので同様の問題にお困りの方はご活用ください。
GitHubのREST API v3でNotificationsを取得するendpointとresponseは以下URLの通りです。
https://developer.github.com/v3/activity/notifications/#list-your-notifications
ここで、通知の対象であるissueやpull requestの情報を見ようとすると、subject.url
がそれっぽいなということになります。しかしよく見ると、domainが api.github.com
になっています。例としてdocumentに載っている https://api.github.com/repos/octokit/octokit.rb/issues/123
ですが、ここにweb browserからaccessすると、JSONが返ってきます。この中に、html_url
として、human accessableなURLが入っています。
これ、しんどくないですか。いわゆるGraphQLが解決しようとした、RESTによるN+1の実例じゃないか!となりました。そしてGraphQL API v4にはまだNotification Objectは来ていないのですね。
この件、supportに投げたのですが、僕の英語力が未熟なのか、「 https://developer.github.com/v3/pulls/#get-a-single-pull-request を使うといいよ」と返事が来ました。そういうことなのでしょう。
ただ、get-a-single-pull-requestしようにも、responseの中にissueやpull requestのnumberが単体では含まれないので、二進も三進も。(このscriptではgsubでhtmlを組み立てています)
よしいっちょブログに書くか〜となり、そんならREADME.mdを整備しておかないと「映え」ないなとなって、heroku appならdeploy to herokuボタン欲しいよなといろいろとmeta dataをつくっていて、正直面倒なんです。
じゃあなんで書くかというと、codeにcommentを書くように、あとで見る自分のためなんですね。未来の自分が環境構築するときに困らないように、という目的があるのかなと思いました。