去る9/25に、歌舞伎座タワーにあるドワンゴのセミナールームにて、「歌舞伎座.tech#1」という技術勉強会を開催いたしました。

「歌舞伎座.tech」とは、ドワンゴが主催する歌舞伎座タワーで行われる勉強会です。初回の今回は「Scalaを取り巻く世界」をテーマに、Scalaに関係することならなんでもOKで色々話してもらおうという会でした。
当日の登壇者と発表内容は以下の通りです(募集ページ)。
- 「Future in Scala」by @j5ik2o
また、LT枠として以下の5つの発表をしていただきました。
当日になって急遽登壇者を変更するというトラブルもありましたが、なんとか無事に終えることができました。
当日はニコ生でも生放送をいたしました。タイムシフトは10/2の23時59分までですが、後ほどアーカイブを動画として上げる予定ですのでお待ちください。
今後も、定期的に「歌舞伎座.tech」を開催していこうと考えておりますので、ご興味をもった方がいらっしゃいましたら、是非ご参加頂ければと考えております。
次回は11/14に「C++11/14」をテーマに開催することを考えています。乞うご期待!
1.はじめに
こんにちは! "ドワンゴ 弁当" で最近少し話題になったドワンゴエンジニア、の氏家です。
どんな人が中で働いてるのか想像しにくい方も多いかもしれませんが、普通の人・オタクな人・ギークな人・家庭持ち・リア充・イケメン、いろんな人が混じってる、楽しい会社だと思っています。
人と同じように 多種多様なサービス・システム・ミドルウェア・デバイス・プログラム言語を駆使してみんながニコニコできるものを産み出そうとがんばっていますので、こういったエンジニアリングに興味がある方は是非コチラからご応募ください!ニコニコ入社一時金制度もやっています。
そしていろいろと長くなってしまいましたが、今回でChef Solo話、完結したいと思います。今回はやってみて気づいた点・はまった点などを詳しく説明しますので、少しでもみなさんの参考になれば幸いです。
2.TIPS!
もくじ
- roleはjsonで書くべき? それともruby?
- recipe・attribute内でのcookbook名の参照
- 秘密情報の管理
- どれを使うといいの? bash, script, execute
- serviceで起動がされない
- ファイル・ディレクトリ属性"mode"の書き方
- OS標準で用意されてるconfの書き換えはどうする?
- templateとcookbook_fileは自動バックアップがされてる
- ifとnot_ifとonly_if どれを使えばいい?
- 同一名だとWarning
- attribute内で他の値を参照するときの書き方
- attributeの配列定義の注意
roleはjsonで書くべき? それともruby?
roleのファイル形式はjson・ruby どちらの形式でも書くことが出来ます。ただroleだとif文による条件分岐を書けたり他のファイルをincludeできるなど、ruby構文によるプログラム制御が出来るので、rubyで書くのがおすすめです。
nodeファイルは残念ながらjson形式しか対応していません。
recipe・attribute内でのcookbook名の参照
recipe内では、自動的に割り当てられている"cookbook_name"という変数で 現在のcookbook名が参照できます。
log cookbook_name
一方attribute内では直接参照はできませんが、
cookbook_name = __FILE__.split('/')[-3]
default[cookbook_name]['prefix'] = '/usr/local/mysql'
とすることで、attribute内でもcookbook名を参照することが出来ます。
これは、attributeが次のようなディレクトリ構造になってるからです。
/cookbooks/mysql/attributes/default.rb
秘密情報の管理
例えばユーザーやDBアカウントのパスワードだったり、開発者には知られてはいけない インフラ担当者のみが知ってよい設定値など、他の人には見られたくない情報があったりします。
そういったものはdata_bagに保存しておくのが正しいのですが、Chef Soloの場合 構成ファイルをgit等で管理する関係上、取り扱いをうっかりしちゃうと誰でもパスワードが見れてしまう危険性があります。
そこでChef Soloでdata_bagの値を暗号化して秘匿化する方法があります。それは、Knife Soloと同じようなアドオン「knife-solo_data_bag」を導入することです。
詳細な説明は省きますが、これを使えばdata_bagの中に置いたテキストファイルを暗号化でき、recipe内で暗号化された中身を直接参照することも出来ます。
ただ大きな問題がありまして、この秘匿化を行う際の鍵は共通鍵です。つまりこの鍵が流出してしまうと、結局内容が丸裸になってるのと同じです。なのでこの鍵だけは別管理で保管しておくのが(面倒ですが)現状では最善な策です。
ちなみにですが、Knife Soloで転送する=秘密鍵もあわせて転送されます。Knife Solo 0.3.0からは、転送先は /home/実行ユーザー/chef-solo/ になります。なので実行ユーザーのhomeディレクトリも他の人は見れないように注意しとく必要があります。
どれを使うといいの? bash, script, execute
例えば「tarを展開する」などChef標準のリソースでは対応してない場合、自分でスクリプトを書くことになります。そのスクリプトを実行するリソースにはいくつか種類があります。どれを使えばよいでしょうか?
- script vs bash (csh, ruby, perl, python)
script "copy file" do
interpreter "bash"
code <<-EOH
cp /tmp/file1 /tmp/file2
EOH
end
と
bash "copy file" do
code <<-EOH
cp /tmp/file1 /tmp/file2
EOH
end
は同じ動きをします。
- bash vs execute
execute "copy file" do
command "cp /tmp/file1 /tmp/file2"
end
executeはよくホームページで見かけるサンプルを見ると、だいたい上記のように1行で実行する場合のみ使える、ように見えますが、実際は
execute "copy file" do
command <<-EOH
cp /tmp/file1 /tmp/file2
cp /tmp/file1 /tmp/file2
EOH
end
のように複数行書くことも出来ます。じゃあ違いは何かというと、
bash
記述されたスクリプトを、一時ファイルとして保存し、bashで実行する
execute
記述されたスクリプトを、"sh -c" で実行する
なので、結論からすると、
問題なければ基本はexecute、問題があればbashなど他のリソースを使う
がよいかと考えます。
ちなみに
execute "copy file" do
command "cp /tmp/file1 /tmp/file2"
creates "/tmp/file2"
end
と書けば、file2がすでに存在してるときはcommandを実行しません。
serviceで起動がされない
例えば
service "httpd" do
action [ :start ]
end
と書いても起動しないときがあり、理由は大きく2つあります。
1.conf設定などに不備があって、そもそも起動が失敗する
2."service httpd status" の終了コードが0 (既に起動中とChefが判断している)
特に2.のときは 手動では起動できてしまうので、要注意です。あと、
service "httpd" do
supports :restart => true
action [ :restart ]
end
上記の場合は"service httpd restart"に相当することをしますが、
service "httpd" do
supports :restart => false
action [ :restart ]
end
の場合は"service httpd stop; service httpd start" と実行されるので注意が必要です。
ファイル・ディレクトリ属性"mode"の書き方
ファイルやディレクトリの属性は"mode"で指定しますが、解説によって書き方がまちまちです。主な例だと下記のようなのが挙げられます。
1.mode 0644
2.mode 00644
3.mode "0644"
rubyは 0から始まる数値は8進数として認識します。なので1番 は"644"、2,3番は"0644"ということになります。
おすすめの書き方は3番目です。
1番の場合、chmodで例えると、
一見は "chmod 0644" と指定しているように見えますが、
実際は "chmod 644" と書いてることになります。
Chef上の表記と、実際の指定値は異なるので紛らわしいかなというところです。
OS標準で用意されてるconfの書き換えはどうする?
例えばsshd_configとかです。
だいたいはデフォルトのsshd_configの中の、一部の値のみを変更します。
が、Chefではそういった変更に対応する方法がありません。
実際どうするか。
0.手作業で編集する
→ chefはサーバーをあるべき状態に収束するって考えからすると、論外ですね。
1.templateリソースなどを使って、Chefで管理してるconfファイルをサーバーに置く
→ OS標準のconfをまるっと置き換えるので、もしかしたら動作がおかしくなるかも…
2.bashリソース内でsedを使って、一部分のパラメータ値だけを変更する
→ べき等性の担保が難しい
ただ、Chefのレシピを見ればサーバーがどういう状態になってるかがわかる、という観点からすると、1.でやるのがよいのかなと思っています。
templateとcookbook_fileは自動バックアップがされてる
templateやcookbook_fileを用いてテキストファイルを置き換えるとき、元々あったファイルが自動でバックアップされるようになっています。
デフォルトでは /var/chef/backup に、日時が付加されたファイル名になって保存されます。
ifとnot_ifとonly_if どれを使えばいい?
「特定の条件のときには○○はしない」という条件判定をするときの書き方について。
「既に同名のファイルが存在する場合はコピーをしない」というのを例にあげると、まずnot_if・only_ifで書く方法があります。
execute "copy file (not_if)" do
command "cp /tmp/file1 /tmp/file2"
not_if do File.exists?("/tmp/file2") end
end
execute "copy file (only_if)" do
command "cp /tmp/file1 /tmp/file2"
only_if do !File.exists?("/tmp/file2") end
end
これらの違いは真と偽が逆転してるだけで、上記の2例は同じ挙動をします。
一方、ifとの違いについてです。
if !File.exists?("/tmp/file2")
execute "copy file (if)" do
command "cp /tmp/file1 /tmp/file2"
end
end
execute "copy file (only_if)" do
command "cp /tmp/file1 /tmp/file2"
only_if do !File.exists?("/tmp/file2") end
end
一見すると上の例はどちらも同じような動きをしそうですが、実は違います。上の例では 想定通り最初のcpでファイルがコピーされるのでファイルが存在することになり、only_ifの方は実行されません。
ところが
execute "copy file (only_if)" do
command "cp /tmp/file1 /tmp/file2"
only_if do !File.exists?("/tmp/file2") end
end
if !File.exists?("/tmp/a")
execute "copy file (if)" do
command "cp /tmp/file1 /tmp/file2"
end
end
と書くと、下のif文で書かれた方もコピーが実行されてしまいます!
これはChefがrecipeをどう解釈してるかの違いから生まれるものです。Knife SoloでChef Soloを実行するとき、内部ではおおざっぱには次のような処理がされます。
1. Knife Solo実行
1−1.rsyncでcookbook一式rsync
2.Chef Solo実行
2−1.recipeを解読
実行するリソースを、Chefプログラム内の実行予定リストに追加
2−2.attribute値を決定
attributeファイル内で定義された値をベースに、
同じ変数名の値が role・nodeで定義されてた場合は、それぞれ値を上書きする
※上記によって確定した値を、recipe内では node[]ハッシュで参照することになる
2−3.2−1.で作られた実行リストの順に実行する
これによって、if文で書いた場合は 2−1.時点で if条件の判定がされてしまいます。一方only_ifで行うと、2−3.の実行リストが1つずつ実行されてく過程で判定がされます。
なので先の例だと、Chef Soloのrecipe解釈時にはファイルが存在しないので実行リストに"executeを実行する" と追加されてしまい(条件判定はこの段階で終わり)、実際にexecuteを実行する段階では ファイルがあろうとなかろうとコピーが実行されてしまったのです。
同一名だとWarning
recipe内で同じ名前を繰り返し使うとwarningになります。よくある例がrubyの配列で処理してるときですね。
%w{file2 file3}.each do |filename|
execute "copy file" do
command "cp /tmp/file1 /tmp/#{filename}"
end
end
これも先に書いたChefの実行解釈によるもので、recipe解釈時に配列を展開して、1つずつ実行リストに付け加えてるからです。なので同じ名前が被らないように工夫しましょう。
%w{file2 file3}.each do |filename|
execute "copy file : #{filename}" do
command "cp /tmp/file1 /tmp/#{filename}"
end
end
attribute内で他の値を参照するときの書き方
attribute内で下記のように書いたとします。
default['attr1'] = 'ニコニコ'; default['attr2'] = default['attr1'] + '動画'; default['attr3'] = node['attr1'] + '動画';
recipe内で下記のように中身を確認すると、どちらも、"ニコニコ動画"の値が返ってきます。
log node['attr2'] log node['attr3']
ただattributeはrole及びnodeで設定値を上書くことができます。もしrole内で
default_attributes(
"attr1"=>"ピアピア"
)
とすると、attr3の値は"ピアピア動画"となりますが、attr2は"ニコニコ動画"のままとなってしまいます。
attributeの配列定義の注意
attributeはrubyファイルなので配列も定義できます。
default['arr'] = [
{'name' => '動画'},
]
ただ、それをroleやnodeで定義すれば値は上書きされるはず、ですよね。
default_attributes(
'arr' => [
{'name' => '生放送'}
{'name' => 'チャンネル'}
]
)
でも、recipe内で下記のように値を参照すると、
node['arr'].each do |value| log value end
"動画"、"生放送"、"チャンネル"、と、attributeで定義した配列に追加される形となってしまいます。
もし配列も、roleやnodeで定義した値で完全に置き換えたい場合は、ハッシュにすると解決できます。
default['arr'] = {
0 => {'name' => '動画'},
}
default_attributes(
'arr' => {
0 => {'name' => '生放送'},
1 => {'name' => 'チャンネル'}
}
)
node['arr'].each_value do |value| log value end ※ハッシュなので値を取り出すときは、.eachではなく.each_valueにする
これで "生放送"、"チャンネル" だけがかえってくるようになります。
3.さいごに
Chefでレシピを書くのは、正直大変です。慣れないうちは「ソースからコンパイルしてインストールする」レシピを書くだけで1日かかってしまいます。ただ自分の場合はChefの用語や特有の記法などに混乱し、その情報を調べるのに無駄に時間がかかっていました。
今回の連載がどこまで皆さんの役に立つかはわかりませんが、これで1日かかっていたのが半日に減少され、Chefに挫折する人が減って、Chef人口が増えてくれれば一番何よりです。
1.はじめに
こんにちは! ドワンゴのインフラ担当の氏家です。
ドワンゴは歌舞伎座タワーに在りまして、東銀座駅と地下で直結しています。そこからオフィスに向かうエレベータが並んでるんですが、一機だけは観光用の屋上庭園に向かう専用のエレベータがあり、ガイドさんが「こちらですよ」と案内してエレベータのボタンを押してくれたりします。
移転後の出社初日だけは「屋上庭園に行きますか?」と聞かれたのですが、それ以降は、パッと一目確認されると、何かを察してか何も言われなくなってしまいました。まあ屋上庭園に行く人とは客層が違うから当たり前ですよね。。
さて今回は実際に実際にKnife Soloを使ってChef Soloを実行するまでを解説したいと思います。
2.Chefの実行に必要なファイルを作成
2−1.Cookbookの作成
recipeはセットアップの手順書で、Cookbookはそれらrecipeとそれ以外にセットアップに必要なものがまとまった集合体です。Cookbookは"apache"や"mysql"といった単位で作成します。
knife cookbook create [cookbook名] -o [作成先ディレクトリ]
※Cookbookを作成する度に必ずやる必要はなく、既にあるCookbookをコピーするだけでも構いません
例えば、下記のように実行すると
$ cd [リポジトリ] #リポジトリとは、前回の記事で"knife solo init"で作ったディレクトリをさします $ knife cookbook create apache2 -o cookbooks
上記で作成されるディレクトリがたくさんあり混乱してしまいますが、これはほぼ必ず使うっていうものだけリストアップすると、recipes、attributes、templatesの三種だけです。
ちなみにいまさらですが、用語について、
Chef=料理人 Cookbook=料理本 Recipe=料理のレシピ
で"セットアップ"を "料理"に例えたんでしょうが、個人的にはかえってこの用語の方が混乱してしまうような気もします。attributeとかtemplateとかは料理と関係ないですし。
recipe:
一番肝心なもので、セットアップの手順を書いておくスクリプトファイルです。recipeはなるべく汎用的に書きます。
attribute:
そしてサーバーごと に異なる設定値はなるべく外出しします。それがattributeです。例えばapacheでいえばhttpd.confの中の設定値とかですね。
template:
erbという、テキスト内にruby構文を埋められる形式でファイルを作成します。ruby構文を用いて、attributeの値を動的に取り込むことができます。
[リポジトリ名]/cookbooks/apache2/templates/default/httpd.conf.erb
ServerRoot <%= @server_root %>
[リポジトリ名]/cookbooks/apache2/attributes/default.rb
default['apache2']['server_root'] = "/etc/httpd"
[リポジトリ名]/cookbooks/apache2/recipes/default.rb
template "/etc/httpd/conf/httpd.conf" do
owner "root"
group "root"
mode 0644
variables({
:server_root => node['apache2']['server_root']
})
end
これを実行すると、
-rw-r--r-- root /etc/httpd/conf/httpd.conf
というファイルが作成され、中身は
ServerRoot "/etc/httpd"
が書き込まれています。
いくつか出てきますが、それぞれ意味が異なるので注意が必要です。
templates/default/
OSによって読み込むtemplate,attributeを分けたいときに使います。
- templates/default/httpd.conf.erb
- templates/ubuntu/httpd.conf.erb
実行するrecipeを分けたいときに使います。
詳しくは次章で解説しますが、
cookbooks/apache2/recipes/default.rb
を呼び出すときは、nodeと呼ばれるファイルに
"recipe[apache2]"
と書きます。
例えばdefaultはパッケージからapacheをインストールするrecipeだとします。
一方ソースからコンパイルするrecipeが別途必要であれば、
cookbooks/apache2/recipes/source.rb
に記述をし、呼び出すときは
"recipe[apache2::source]"
と書く、といった使い分けをします。
attributes/default.rb
これは上記に関連しますが、recipe毎にパラメータを変えたい場合書きます。
cookbooks/apache2/attributes/default.rb
default['apache2']['server_root'] = "/etc/httpd"
cookbooks/apache2/attributes/source.rb
default['apache2']['server_root'] = "/usr/local/apache2"
2−2.nodeの作成
[リポジトリ名]/nodes/192.168.1.1.json
{
"run_list":[
"recipe[apache2]"
]
}
これでChefを実行する準備ができました。
3.Knife Soloを用いてChef Soloを実行
Knife Solo実行するには次のようなことをします。
- Cookbookなどのファイルを作業マシン上で作成
- それをsshで対象のサーバーにファイル転送 (1)
- 対象サーバー上でChef Soloを実行 (2)
(1)
ssh接続は、毎回rootでするわけにはいきません。セキュリティ上、直接rootアカウントにssh接続はできないようにするかと思います。また普通にやるとssh接続のパスワードが毎回聞かれてしまいます。
そのため、作業マシンでssh認証用の秘密鍵・公開鍵を作り、対象サーバーに公開鍵認証でパスフレーズ無しで接続できるアカウントを作るのがベストです。
(2)
Chefはサーバーへのインストールを行うためroot権限が必須です。そのため上記で作成したアカウントはNOPASSWDでrootになれるようにsudoersを設定する必要があります。
そのため、セットアップを行う前に対象サーバーで上で次のスクリプトを実行しましょう。
ADMIN_USER=[chefを実行するユーザーアカウント名] useradd $ADMIN_USER -G wheel sudo -u $ADMIN_USER sh -c "cd /home/$ADMIN_USER && mkdir .ssh && chmod 700 .ssh && echo \"[作業マシンで作成した鍵の公開鍵]\" > .ssh/authorized_keys && chmod 600 .ssh/authorized_keys" echo '%wheel ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
そうすれば以後は、作業マシン上で[リポジトリ]ディレクトリの中に入り
knife solo cook [ユーザーID]@[ホスト名]
を実行すれば、対象サーバーでChef Soloが実行されるようになります。
余裕があれば上記のユーザー作成もrecipe化してしまうとよいと思います。
4.補足1−attributeのオーバーライド
上記までで最低限の実行はできますが、roleを使うともっと設定の幅が広がります。
roleについては前回も説明しましたがおさらいでもう一度説明すると、”webサーバーで実行するrecipe"、"DBサーバーで実行するrecipe"をまとめることができます。
例えば
- roles/web.rb には "recipe[apache2]", "recipe[php]" を記述
- roles/db.rb には "recipe[mysql]" を記述
- nodes/192.168.1.1.jsonで "role[web]" を記述
そしてこのroleにはattribute値も書くことができます。実はnodeにもattribute値を書くことができます。同じ変数名が定義されてると、
attributes/default.rb のattribute値 < role のattribute値 < node のattribute値
というような優先度になります。
例えばapacheのアクセスIP制限だと、
- Cookbook自体は普通の汎用的なapacheサーバー向けに作ったので、デフォルトとなるattributeファイルは全公開として定義
- roleは、社内専用のWebサーバーとして使うので特定のセグメントのみ公開
- nodeは、「このサーバーだけは管理用サーバー」なので、さらに限定した範囲のみ公開
apache2/attributes/default.rb
default['apache2']['allow_ip'] = "all"
roles/office_web.rb
default_attributes(
'apache2' => {
'allow_ip' => "192.168.0.0/16"
}
)
nodes/192.168.1.1.json
{
"run_list":[
"recipe[apache2]"
],
"apache2" : {
"allow_ip" : "192.168.1.0/24"
}
}
そうすれば、recipe内 では
node['apache2']['allow_ip']
※レシピ内ではattribute値は、"node[]"というハッシュに展開されます
5.補足2−recipeに書くリソース
2章で書いた、httpd.confを作成するときに
template "/etc/httpd/conf/httpd.conf" do
owner "root"
group "root"
mode 0644
variables({
:server_root => node['apache2']['server_root']
})
end
この1行目の"template"がリソースと呼ばれるものです。
templateリソースは、変数値を後で付加できるテンプレートファイルを作成する機能をもちます。
※なぜリソースというのか
Chefが何をするかというと、サーバーの状態を管理し、ノード(サーバー)をあるべき状態に収束させることです。
あるべき状態=ソフトウェアのセットアップのみでなく、ファイルを所定の場所に置いたり、設定値を書き換えたり、サービスを起動させたりします。
そのために必要なのがソフトウェアのパッケージだったり、設定ファイルだったり、サービスの起動スクリプトだったり、そういったリソースが必要です。
サーバーをあるべき状態にするために、レシピに、リソースを書くことで、収束させることができるわけです。
なので、サーバーをあるべき状態にするものをリソースといいます。
ただプログラマからしたら”メソッド”と覚えた方が直観的かもしれないですね。
このリソースには様々な種類があり、代表的なものをいくつかリストアップしてみます。
詳しくは公式サイトを参照ください。
- bash…個人的には一番よく使うもので、要はbashが起動し任意のスクリプトを実行できます。
- directory…ディレクトリを作成・削除できます。
- file…任意の場所にテキストファイルを作成することができます。
- package…packageをインストールします。OSによってrpmだったりdebだったり。
- remote_file…http経由でファイルをダウンロードします。
- user…ユーザーを作成します。
注意しなければいけないのは、標準のリソースはべき等性が保たれてて何回同じリソースを実行しても問題ないのですが、bashの場合は自分でそれを担保しなければなりません。
各リソースの詳しい説明は割愛しますが、各自いろいろ試してみてください。
さて、今回は前回と合わせて、初めてChef Soloを実行するまでの解説におさまってしまったので、次回は、私がはまったところやコツ、TIPS等の解説をできればと思います。
これで少しでもChef構築の手助けになれば幸いです。
そしてドワンゴでは、現在エンジニアを積極的に採用中です。 特に、このような環境構築自動化のノウハウを持ったインフラエンジニアは大歓迎です。 ご興味がある方は是非コチラからご応募ください!ニコニコ入社一時金制度もやっています。
月別アーカイブ
- 2018年11月のブロマガ記事(1)
- 2017年10月のブロマガ記事(2)
- 2017年09月のブロマガ記事(2)
- 2017年08月のブロマガ記事(4)
- 2017年07月のブロマガ記事(3)
- 2016年12月のブロマガ記事(1)
- 2016年07月のブロマガ記事(1)
- 2016年06月のブロマガ記事(1)
- 2015年12月のブロマガ記事(1)
- 2015年07月のブロマガ記事(1)
- 2015年05月のブロマガ記事(1)
- 2015年01月のブロマガ記事(1)
- 2014年11月のブロマガ記事(2)
- 2014年10月のブロマガ記事(2)
- 2014年09月のブロマガ記事(1)
- 2014年08月のブロマガ記事(2)
- 2014年07月のブロマガ記事(3)
- 2014年06月のブロマガ記事(1)
- 2014年03月のブロマガ記事(1)
- 2013年11月のブロマガ記事(1)
- 2013年10月のブロマガ記事(2)
- 2013年09月のブロマガ記事(1)
- 2013年08月のブロマガ記事(3)
- 2013年07月のブロマガ記事(1)
- 2013年06月のブロマガ記事(2)
タグ
- ドワンゴ(33)
- dwango(33)
- engineer(27)
- エンジニア(27)
- ご報告(7)
- 新配信(7)
- kbkz_tech(4)
- 動画検索(4)
- ニコニコ動画(4)
- Scala(4)
- 歌舞伎座.tech(4)
- Chef(4)
- ハッカソン(4)
- hackathon(3)
- インフラ(2)
- C++(2)
- 研修(2)
- はてな(1)
- internship(1)
- 新人研修(1)
- 勉強会(1)
- カンファレンス(1)
- SIGGRAPH(1)
- ICML(1)
- CVPR(1)
- インターン(1)
- mastodon(1)
- friends.nico(1)
- programming(1)
- プログラミング言語(1)
- プログラミング(1)
- tokyo_technical_samurai(1)
- robot(1)
- elasticsearch(1)
- Unity(1)
- OculusRift(1)
- インターンシップ(1)
- event(1)
- 将棋(1)
- API(1)
- ma9(1)
- ドワンゴ技術部(1)