ラスベガスです。初参加です。帰ってきたらまたどこかにレポート的な物が上がると思います。
NoOps Meetup Tokyo #8 で Observability と Mackerel の話をしました
タイトルの通りです。 9/17 にあった NoOps Meetup Tokyo #8 にて、"Observability: Mackerel による観測と Mackerel の観測"というタイトルで発表してきました。
当日のスライドはこちらです。
NoOps という名前は刺激的ですが、 NoOps Japanコミュニティでは以下のように No Uncomfortable Ops という形で表現されています。
NoOps = No "Uncomfortable" Ops
NoOps Japanでは 「システム運用保守の"嬉しくないこと"をなくそう!」 をテーマに、 NoOpsを実現するための技術・設計手法・開発運用保守サイクル・ツールや考え方・事例などを共有していきたいと考えています。
NoOps Meetup Tokyo #8 - connpass
今回で1周年を迎えた(おめでとうございます!)この NoOps Japan コミュニティの Meetup で、 Observability と広く言われる概念のうち Metrics を主戦場とする Mackerel の位置づけや実際の Mackerel の利用、そして最後には Mackerel チーム自身が Mackerel をどう "観測" しているか、という中身でお話しさせていただきました。
自分たちのシステムと向き合うという意味でも、自分たちのサービスと向き合うという意味でも沢山刺激と学びを得ることができました。また Meetup に足を運べればいいな、と思っています。
おまけ
発表中でチラ見せした時系列データベースの話はこのあたりからどうぞ!
blog.yuuk.io
itchyny.hatenablog.com
astj.hatenablog.com
開発チームでプロダクトを新しく保っていくぞ、という話をしてきた
6/15 に Developers Boost KANSAI という U30 向けの技術カンファレンスがあったのだけど、ぎりぎりまだU30やで、ということもあり発表機会をいただいて、ここ半年〜1年くらい Mackerel チームで取り組んでることの話をしてきました。
発表スライドはこちらです。例によってスライドだけではあんまり伝わらない気がしますが、雰囲気を味わうのにどうぞ。
初期開発のあと何年も継続開発なり保守運用なりを続けていくと、何も手を打たなければソフトウェア基盤 / インフラ基盤はだんだん古びていくことになります。古びていくことで徐々に身動きが取りづらくなることを避けようという側面と、そもそもソフトウェアは新しい方が良いのだからちゃんと新陳代謝させて新しくしていこうよ、というメッセージをもとに、実際にチームで継続的に更新していくために行ってる取り組みの話をしました。
ひとつは開発チーム全体のタスク管理に持ち込む前段階としてエンジニア内でタスクの整理をする試みで、もう一つは依存ライブラリの更新を安定して行うためのライブラリアップデート当番という試みです。
勿論この取り組みが全ての状況でフィットするとは限らず、チームやプロダクトの状況に応じて色々なアプローチがあると思っていますが、一つの実例紹介として受け取っていただけたなら幸いです。
Hatena Engineer Seminar #11 で「Mackerel をオンプレミスから AWS に移してからの1年半を振り返る」という発表をしました
1月なのでもう1ヶ月以上前の話になってしまいたいへん恐縮ですが、1/23 と 1/30 にあった Hatena Engineer Seminar #11 で Mackerel チームのエンジニアとして発表していました。
スライドはこれです。
オンプレミスでの Xen 運用から EC2 に移ったことで仮想マシンの管理・運用が柔軟になったのは、仮想マシンより下の層が AWS によって抽象化されたといえ、 EC2 上で運用していた RDB が RDS に移行したことでインスタンスやレプリケーション構築といった層の管理から解放されたのも AWS によって抽象化されたと言ってよいと思います。これらの領域では、今まで行ってきたことをクラウド活用で抽象化し、より扱いやすくなったというのが良い表現だと思います。
いっぽう、 AWS を前提として新規に開発しているコンポーネントにおいては、オンプレミス + 仮想マシンという状況では選択肢に上がらなかったマネージドサービスを活用できるようになっており、こちらは上記の「より扱いやすくなった」より一歩先を行くありがたみであると感じています。(もちろん、気合を十分に込めればオンプレミスでもマネージドサービス相当の機能を実装できたとは考えられますが、開発・運用ともに現実的ではないコンポーネントも多いです。)
ちなみにスライド中で機械学習は「開発中」となっていましたが、先日ロール内異常検知機能がベータ版としてリリースされております。こちらもあわせて是非ご利用下さい。クラウドという文脈で言うと AWS Batch などをまさに利用しています。
Go で JSON を扱うにあたっての個人的なメモ
個人的なメモです。というのは全てにおいて予防線として扱われる。
omitempty
JSON tag につける omitempty は、 JSON の Marshal 時に参照される。 empty value だった際にそのフィールドがまるごとスキップされる。
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.
https://golang.org/pkg/encoding/json/#Marshal
(なので、 UnMarshal にしか使わないような構造体の JSON tag につけても意味はない)
Unmarshal 時のフィールド名の対応
JSON のあるフィールドが JSON のどのフィールドに対応づけられるかのルール:
How does Unmarshal identify the fields in which to store the decoded data? For a given JSON key "Foo", Unmarshal will look through the destination struct's fields to find (in order of preference):
https://blog.golang.org/json-and-go
- An exported field with a tag of "Foo" (see the Go spec for more on struct tags),
- An exported field named "Foo", or
- An exported field named "FOO" or "FoO" or some other case-insensitive match of "Foo".
つまり、(Unmarshal する都合においてのみ言うと)、JSON 上で hogeHoge というフィールドのものを構造体の HogeHoge や Hogehoge などにマッピングしたい場合、 JSON タグはつけなくてもマッピングされる。
逆に、この Case-Insensitive match を無効にするオプション今のところないようなので、 Case-sensitive な厳格な JSON デコーダは実現できないことになる。今触ってるコードだとこれが結構キツいんだけどどうしようか悩んでいる。諦める???
未知のフィールドを除外する
func (*Decoder) DisallowUnknownFields()
を使う。
DisallowUnknownFields causes the Decoder to return an error when the destination is a struct and the input contains object keys which do not match any non-ignored, exported fields in the destination.
https://golang.org/pkg/encoding/json/#Decoder.DisallowUnknownFields
ちなみに前述したような Case-insensitive match が無効になる訳ではない。
missing key
package main import ( "bytes" "encoding/json" "fmt" ) func main() { var data struct { Name string `json:"name"` Memo string `json:"memo"` Nicknames []string `json:"nicknames"` } buffer := bytes.NewBufferString("{\"name\":\"tom\"}") decoder := json.NewDecoder(buffer) if err := decoder.Decode(&data); err != nil { fmt.Println(err.Error()) } fmt.Println(data) }
https://play.golang.org/p/PtPrJd0TIE0
構造体の期待するフィールドが JSON 側になかったとき、デコーダは特にエラーにならずに empty value を自然と埋める。緩く扱うにはメリットだけれど、ユーザ入力をきちんとハンドリングしたい時にはちょっと困る気もする。
null as array
package main import ( "bytes" "encoding/json" "fmt" ) func main() { var data struct { Name string `json:"name"` Nicknames []string `json:"nicknames"` } buffer := bytes.NewBufferString("{\"name\":\"tom\", \"nicknames\": null}") decoder := json.NewDecoder(buffer) if err := decoder.Decode(&data); err != nil { fmt.Println(err.Error()) } fmt.Println(data) }
https://play.golang.org/p/go69_sBuVZ1
リストっぽいところに null を渡すと空のリストになる。上記の missing key と組み合わせると、ふつうにやると以下の3つは Unmarshal された構造体からは区別できない
null
[]
- (そもそもフィールドが渡されてこなかった)
2018/09/04 01:19: 複数のフィールドが同じタグを持っている
package main import ( "encoding/json" "fmt" ) type Single struct { Name1 string `json:"name"` Name2 string `json:"name"` } type Inner1 struct { Name1 string `json:"name"` } type Inner2 struct { Name2 string `json:"name"` } type Inner3 struct { Name3 string `json:"name3"` } type OuterA struct { *Inner1 *Inner2 } type OuterB struct { *Inner1 *Inner3 } func main() { data := &Single{} if err := json.Unmarshal([]byte("{\"name\":\"tom\"}"), &data); err != nil { fmt.Println(err.Error()) } fmt.Println(data) dataA := &OuterA{} if err := json.Unmarshal([]byte("{\"name\":\"tom\"}"), &dataA); err != nil { fmt.Println(err.Error()) } fmt.Println(dataA.Inner1, dataA.Inner2) dataB := &OuterB{} if err := json.Unmarshal([]byte("{\"name\":\"tom\"}"), &dataB); err != nil { fmt.Println(err.Error()) } fmt.Println(dataB.Inner1, dataB.Inner3) }
https://play.golang.org/p/2Ovn3J7ChfQ
複数のフィールドにうっかり同じタグをつけてしまった場合、どちらにも Unmarshal されない。ちなみに playground つけてないけど Marshal もされない気がする。
上記の Playground の `Single` のように単一の struct にタグが複数ついてると go vet が怒ってくれるけれど、 Outer~ のように embedding だと go vet でも気付かないので難しい。(過去にこれでミスったことがある)
docker-compose で - や _ で始まるプロジェクト名を使ってはいけない
こういうなんでもないプロジェクトがあったとする。
[astj@gemmy01 /Users/astj/sandbox/myproject]$ cat Dockerfile FROM debian:8 [astj@gemmy01 /Users/astj/sandbox/myproject]$ cat docker-compose.yml version: "3" services: debian: build: context: . dockerfile: Dockerfile [astj@gemmy01 /Users/astj/sandbox/myproject]$ docker-compose build Building debian Step 1/1 : FROM debian:8 ---> ce40fb3adcc6 Successfully built ce40fb3adcc6 Successfully tagged myproject_debian:latest
ここで docker-compose
のプロジェクト名を - や _ で始まるものにすると、直感的じゃないエラーメッセージを吐いて失敗する。
[astj@gemmy01 /Users/astj/sandbox/myproject]$ docker-compose -p -myproject build Building debian ERROR: invalid reference format
[astj@gemmy01 /Users/astj/sandbox/myproject]$ docker-compose -p _myproject build Building debian ERROR: invalid reference format
verbose オプションをつけて辿ると、 Docker API から 500 が帰ってきていることが分かる。そうだよねという感じ。
[astj@gemmy01 /Users/astj/sandbox/myproject]$ docker-compose -p -myproject --verbose build compose.config.config.find: Using configuration files: ./docker-compose.yml docker.utils.config.find_config_file: Trying paths: ['/Users/astj/.docker/config.json', '/Users/astj/.dockercfg'] docker.utils.config.find_config_file: Found file at path: /Users/astj/.docker/config.json docker.auth.load_config: Found 'auths' section (snip) docker.auth.load_config: Found 'credsStore' section urllib3.connectionpool._make_request: http://localhost:None "GET /v1.25/version HTTP/1.1" 200 566 compose.cli.command.get_client: docker-compose version 1.21.1, build 5a3f1a3 docker-py version: 3.3.0 CPython version: 3.6.4 OpenSSL version: OpenSSL 1.0.2o 27 Mar 2018 compose.cli.command.get_client: Docker base_url: http+docker://localhost compose.cli.command.get_client: Docker version: Platform={'Name': ''}, Components=[{'Name': 'Engine', 'Version': '18.03.1-ce', 'Details': {'ApiVersion': '1.37', 'Arch': 'amd64', 'BuildTime': '2018-04-26T07:22:38.000000000+00:00', 'Experimental': 'true', 'GitCommit': '9ee9f40', 'GoVersion': 'go1.9.5', 'KernelVersion': '4.9.87-linuxkit-aufs', 'MinAPIVersion': '1.12', 'Os': 'linux'}}], Version=18.03.1-ce, ApiVersion=1.37, MinAPIVersion=1.12, GitCommit=9ee9f40, GoVersion=go1.9.5, Os=linux, Arch=amd64, KernelVersion=4.9.87-linuxkit-aufs, Experimental=True, BuildTime=2018-04-26T07:22:38.000000000+00:00 compose.cli.verbose_proxy.proxy_callable: docker inspect_network <- ('myproject_default') urllib3.connectionpool._make_request: http://localhost:None "GET /v1.25/networks/myproject_default HTTP/1.1" 404 50 compose.service.build: Building debian compose.cli.verbose_proxy.proxy_callable: docker build <- (path='/Users/astj/sandbox/myproject', tag='-myproject_debian', rm=True, forcerm=False, pull=False, nocache=False, dockerfile='Dockerfile', cache_from=None, labels=None, buildargs={}, network_mode=None, target=None, shmsize=None, extra_hosts=None, container_limits={'memory': None}, gzip=False, isolation=None, platform=None) docker.api.build._set_auth_headers: Looking for auth config (snip) urllib3.connectionpool._make_request: http://localhost:None "POST /v1.25/build?t=-myproject_debian&q=False&nocache=False&rm=True&forcerm=False&pull=False&dockerfile=Dockerfile HTTP/1.1" 500 39 compose.cli.verbose_proxy.proxy_callable: docker build -> <generator object APIClient._stream_helper at 0x1049ba570> ERROR: compose.cli.errors.log_api_error: invalid reference format
明示的にこういうプロジェクト名をつけることはないと思うけど、 docker-compose のプロジェクト名のデフォルトはカレントディレクトリの basename から決まるので、例えばディレクトリ名が - で始まる場合も同じ状況になる。
[astj@gemmy01 /Users/astj/sandbox/-myproject]$ docker-compose build Building debian ERROR: invalid reference format
(逆に、この場合でも安全なプロジェクト名を明示的に渡せばきちんと動く。)
[astj@gemmy01 /Users/astj/sandbox/-myproject]$ docker-compose -p mymyproject build Building debian Step 1/1 : FROM debian:8 ---> ce40fb3adcc6 Successfully built ce40fb3adcc6 Successfully tagged mymyproject_debian:latest
ディレクトリ名が - で始まることも普段そうそうないような気がするが、たとえば Jenkins ではビルドプロジェクトの名前に - が入っていると、 truncate された結果こういうディレクトリが出現することがある。。以下のディレクトリは、Hoge-Foobar プロジェクトの様子である。つまり今回ハマった実例……
drwxr-xr-x 14 jenkins jenkins 4096 Jun 14 00:22 -Foobar_better_dockerfile-7R67AOHUZ25GOP2ZOFPGO2JCU7R5KFETCWFFHQJGSXNWUUACIIAA
原因が分かってしまえば納得感があるのだけれど、エラーメッセージがわかりにくいし、 docker-compose がバリデーションしてくれる訳でもないし、さらにカレントディレクトリが暗黙に使われていると非常に気付きづらい。気をつけましょう。。
JVM の DNS キャッシュを制御する
JVM (Java 仮想マシン) には DNS の名前解決の結果をキャッシュする挙動が備わっている。キャッシュするだけならいいのだけれど、このキャッシュでは DNS の TTL を無視してキャッシュするため、名前解決の結果が変わっても JVM からの接続先が切り替わるまでに(TTL から想定される時間以上に)時間がかかる、あるいは全く切り替わらないということがある。この挙動やその制御について調べたので、その話をする。
(以下の話題では Oracle JDK および OpenJDK を対象にして論じるので、それ以外の JVM 実装でどうなってるかは調べていない。適用できる箇所もあればそうでない箇所もありそう)