UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

ハートビーツさんと合同勉強会してきたよ!

どうも、新人エンジニアのハトネコエです。

株式会社ハートビーツさんからお誘いいただき、
8/4(金)、株式会社ハートビーツさんとUUUM株式会社のエンジニアが集って合同勉強会をおこないました。

ハートビーツさんの技術ブログはこちら → インフラエンジニアway - Powered by HEARTBEATS

続きを読む

ロードバランサーって何?

ロードバランサーってあれでしょ?
いい感じに通信を分散してくれるんでしょ?

くらいの知識しかなかった執筆者、ハトネコエです。

ふわっとしすぎている知識から一歩進んで、もうちょっと詳細を知りたくなったので、
以前から気になっていた部分を調べ、社内勉強会で発表してみました。

〜〜お品書き(ここが謎だよロードバランサー)〜〜

  • 1. ロードってなに? 道?
  • 2. どんな仕組みで振り分ける先を決めるの?
    • 2-1. DNSラウンドロビン
    • 2-2. RFC 3484 に従ったクライアントに対して従来のDNSラウンドロビンは使えない
    • 2-3. コマンドで確認
    • 2-4. AWSのELBはどの仕組み?
  • 3. 複数のロードバランサを立てたとき、それらへの振り分けは誰がするの?
  • 4. データはどんな道のりでサーバーからクライアントに渡されるの?
  • 5. 専用の機械を使わなくてもロードバランサーって立てられるの?
  • おわりに
続きを読む

「メタプログラミングRuby」オススメです

こんにちは、タナカです。
夏は花火を見ながら飲むビールが最高ですね。

さて、本日はRubyをある程度使いこなせるようになった方へおすすめの「メタプログラミングRuby」を紹介したいと思います。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

メタプログラミングとは?

そもそもメタプログラミングとは何でしょうか、本書籍には以下一文で説明されています。

メタプログラミングとは、コードを記述するコードを記述することである。

Javaのソースコード上で使用できるアノテーションや、C++のテンプレートのようなコードを思い浮かべるとわかりやすいかもしれません。
これらのように、プログラムの一部を自動生成するものをメタプログラミングと呼びます。

ただし、これらの場合はコードジェネレータやコンパイラを使ったものなので、「静的メタプログラミング」と呼んで本書籍では区別しています。
Rubyの場合は言語自体がメタプログラミングをサポートしていて自由度の高い事ができるため、「動的メタプログラミング」と呼んでいます。

Rubyのメタプログラミング

まずは、ソースコードを見ていただくのが早いと思います。

class Movie < ActiveRecord::Base
end

movie = Movie.create
# moviesテーブルにtitleカラムが定義されている場合
movie.title = “博士の異常な愛情”
movie.title #=> “博士の異常な愛情”

このコード上ではMovieクラスにtitleメソッドが定義されていないにも係わらずエラーにならず設定、参照できています。

エラーにならず動作するのは、Movieクラスで定義されていないメソッドが呼ばれた際に、method_missing()というメソッドが呼ばれるのですが、このメソッドをActiveRecord::Base上で定義してあるからです。
ActiveRecord::Baseでは、テーブルのカラム名に一致するメソッドが実行されたときにmethod_missing()を通して値が設定されるようになっています。

Rubyでは、メタプログラミングの技術でプログラムを動的に拡張することができます。
ActiveRecordをはじめ様々なgem等で、このような技術が利用されています。

書籍の構成

本書籍は2部+付録の構成になっていて、1部は以下の章で構成されています。

  • 1章 頭文字M
  • 2章 オブジェクトモデル
  • 3章 メソッド
  • 4章 ブロック
  • 5章 クラス定義
  • 6章 コードを記述するコード

この中から第2章のオブジェクトモデルを少し紹介したいと思います。

オブジェクトモデル

この章では、クラスやオブジェクトを動的に拡張する方法から、メソッド探索等Rubyの内部処理について学ぶことができます。

オープンクラス

オープンクラスとは、クラスをオープンしてその場で拡張できる技法のことです。
たとえば、Stringクラスを拡張するようなこともでき、以下のようにして文字列にメソッドを追加できます。

class String
  def to_alphanumeric
    gsub(/[-\w\s]/, “”)
  end
end#3, the *magic, Number*?”. to_alphanumeric
#=> “3 the Magic Number”

オブジェクトモデルの内部

オブジェクトモデルのメソッドや、インスタンス変数は以下のようにしてプログラム中で取得することができます。
また、RubyではJavaとは違いインスタンス変数は値が代入されるまで存在しないという特徴があります。

class MyClass
  def my_method
    @v = 1
  end
end

obj = MyClass.new
obj.class # => MyClass

obj.my_method
obj.instance_variables # => [:@v]
obj.methods.grep(/my/) # => [:my_method]


オブジェクトからは、classを通して、クラス定義を調べることもできます。

f:id:tnuuu:20170729235035p:plain

クラスの真相

Rubyではクラスはオブジェクトであるという特徴があり、Class.newで新しいクラスを動的に生成することもできてしまいます。

“hello”.class # => String
String.class # => Class

Class.instance_mothods(false) # => [:allocate, :new, superclass]

オブジェクトとクラスは以下の関係があります。

f:id:tnuuu:20170730000851p:plain

オブジェクトobjのclassはMyClassクラスであり、MyClassクラスのclassはClassクラスになります。
まだ、MyClassクラスのsuperclassはObjectクラスで、さらに、ObjectクラスのsuperclassはルートクラスのBasicObjectクラスです。 (図にはありませんが)

この関係を覚えておくとRubyの理解が深まると思います。

メソッドを呼び出すときに何が起きているの?

メソッドを呼び出すとRubyはまず「メソッド探索」を行い、次に「メソッドの実行」を行います。

メソッド探索

メソッド探索では「レシーバー」と「継承チェーン」の2つの考えを把握しておく必要があります。

用語 説明
レシーバ 呼び出すメソッドが属するオブジェクト
継承チェーン クラスからスーパークラス、スパークラスのスーパークラス、ルートクラスのBasicObjectまで通るクラスの道筋
メソッド探索 レシーバーのクラスに入り、メソッドを見つけるまで継承チェーンを登ること

以下はメソッド探索のイメージ図です。

f:id:tnuuu:20170730012156p:plain

矢印の1でレシーバーのクラスに入り、矢印の2で継承チェーンを登りメソッドを見つけます。

本書籍では、ほかにモジュールを組み合わせたパターンや、多重インクルード時のメソッド探索についても解説されています。

なお、継承チェーンの内容はancestorsメソッドで確認することができます。

MySubclass.ancestors # => [MySubclass, MyClass, Object, Kernel, BasicObject]

メソッドの実行

メソッドを実行する際は、まずメソッドが呼び出されているオブジェクト(レシーバ)が何なのかを意識する必要があります。 以下の例は、レシーバであるobjがselfになること、レシーバを明示していないmy_methodメソッドのレシーバがobjになることを表したものです。

class MyClass
  def testing_self
    @var = 10 # selfのインスタンス変数
    my_method # self.my_methodと同じ
    self
  end

  def my_method
    @var = @var + 1
 end
end

obj = MyClass.new
obj.testing_self # => #<MyClass:0x007f93ab08a728 @var=11>

まとめ

今回紹介した内容は本書籍の内容のごく一部です。
次の3章では動的メソッドを利用して重複コードを排除する方法、2部ではRailsにおけるメタプログラミングについて解説されていて、いずれも興味深いものです。
Rubyについて深く理解するためにも、メタプログラミングRubyはぜひオススメです。

API BluePrintで定義ファーストなAPI開発

こんにちは、エンジニアのナカハシです。

隠れドラゴンズファンなので久々に観戦に行ったら、10点差を追いつかれてサヨナラ負けしました。世知辛いにもほどがありますね!!!!!!!(怒)(泣)


サーバーサイドとフロント/アプリを別チームで開発するといったケースでは、互いのシステムのインターフェイスはAPIになります。なので、API定義の共有は重要です。

APIのドキュメンテーションやチーム開発を支援する、API BluePrintについて、色々試してみました。

続きを読む

4000以上のチャンネルを支えるデータ解析基盤をBigQuery, Go, embulk等で整備した話

nazoです。

UUUMでは2017年3月時点で4000チャンネルを抱えており、日本最大のマルチチャンネルネットワーク(MCN)となっております。

これだけのチャンネル数があり、さらに多くのチャンネルは毎日のように動画を投稿しており、1日に増える動画の数だけでも相当な数になっています。

以前はこれらのデータの取得をPHPで行っていたのですが、最近Goによる新システムの運用を少しずつ始めており、大きな変化が生まれています。 今回はそのあたりについて解説したいと思います。

続きを読む

Google Apps Script で Kintone API のライブラリ作った

おはこんばんちは!! 尾藤 a.k.a. BTO です。

みなさん、Google Apps Script 書いてますか〜。 Cybozu の Kintone 使ってますか〜。

Cybozu の Kintone ってちょっとした業務アプリを手軽に作るのに便利なんですよね。 弊社でも社内の情報を管理するのに Kintone 使ってます。

一方で、 Kintone ってプログラミングは必要ないんだけど、やっぱりシステムの知識がある程度ないと使いこなすのは難しいですよね。 なので、情シスで対応できないところは Google Spreadsheet でデータの管理をしていたりします。

そうすると欲しくなりますよね。 Kintone とデータ連携するためのツールが!! というわけで、Google Apps Script で Kintone API 用のライブラリを作りました〜!!(パチパチパチパチ

Google Apps Script Kintone Library

せっかくなので、必要な方に使ってもらおうと思って、公開しました〜。

gas-kintone

使い方

詳しい使い方は README.mdをご覧ください。

それだけだと寂しいので、簡単なサンプルスクリプト貼っておきます。

var subdomain = 'subdomain';
var appId = 1;
var apiToken = 'xxxx';
var kintone = new Kintone(subdomain, appId, apiToken);

// レコード作成
var id = kintone.recordCreate({foo: 'bar'});
alert(id);

// レコード取得
var record = kintone.recordGet(id);
alert(record.foo);

// レコード更新
var revision = kintone.recordUpdate(id, {foo: 'baz'});
alert(revision);

// レコード削除
var result = kintone.recordDelete(id);
alert(result);

開発はローカルでできるようにする

この Kintone ライブラリを開発するにあたっていくつか工夫した点を書いておきます。

まず Google Apps Script は ES5(Javascript) です。 なので、Nodejs との親和性が高く、開発環境の構築は Nodejs のツールでやるのが良いです。

Google Apps Script はデフォルトで Google が Script Editor を用意してくれています。 これ自体は悪くはないですが、やはりローカルで自分の好きな環境で開発したいですよね。 そこで、ローカルで開発して、Google Apps Script にディプロイして動作確認をするやり方をとりました。

ローカルからディプロイ

ローカルで開発するにあたって、最も重要なのは、Google Apps Script のプロジェクトにどうやってディプロイするかです。 Google Apps Script は Google Drive 上に存在しており、Google Drive API 経由で書き換えが可能です。 この仕組みを使って Google Apps Script をディプロイするツールがいくつか公開されています。 gas-kintone では、Google の公式ツールである node-google-apps-scriptを使いました。

node-google-apps-script の使い方は割愛しますが、Google Drive API 経由でアクセスするため、Google Drip API の権限を持った認証キーを使えば、ローカルで編集したファイルをコマンドラインからディプロイできるようになります。

ユニットテスト

ユニットテストは GasTというライブラリを使いました。 Google Apps Script のユニットテストのライブラリはいくつかあるのですが、継続的にメンテナンスされているものがなく、高機能なものよりもシンプルで安定的に使えそうなものを選びました。

こんな感じのユニットテストを実行するための関数を定義しておきます。

function testRunner() {
  var test = new GasTap();

  var functions = testRunner.functions;
  for (var i = 0; i < functions.length; i++) {
    functions[i](test);
  }

  test.finish();
}

testRunner.functions = [];

そして他のファイルから、この testRunner.functions に追加すれば勝手に実行されるテストとして登録される仕組みです。

testRunner.functions.push(function (test) {
  // ここにテストを書く
});

ディプロイした後に、Google Apps Script プロジェクトにアクセスして testRunner を実行すれば、登録したテストが実行されます。 テストの実行結果はログで確認できます。

f:id:masatobito:20170623172125p:plain

最後の行で、82のテストを実行して、失敗が0だったのがわかります。

ユニットテストの実行のところは、コマンドラインで完結してなくてブラウザにアクセスしないといけないのでちょっとかったるいです。 ですが、Google Apps Script は Google の環境でしか実行できないので、ある程度は仕方がないです。

ちなみに GasT には syslog にログを吐き出す機能も持っているので、syslog でログを受け取るサーバを立てれば、もう少し自動化できるんじゃないかと思います。

eslint でコードチェック

Google Apps Script は ES5 なので、Nodejs で使われてるコードチェックツールがそのまま使えます。 なので、 eslint でコードチェックをするようにしました。 特にこだわりはないので、 airbnb-es5 をノンカスタマイズで使っています。

{
  globals: {
    // Google Apps Script
    PropertiesService: true,
    UrlFetchApp: true,

    // Test Library
    GasTap: true,

    // Our classes
    Kintone: true,
    KintoneClient: true,
    KintoneError: true,
    KintoneResponse: true,

    // test function
    testRunner: true,
  },
  extends: 'airbnb-es5',
}

固有値は Script properties に突っ込む

Kintone API 用のライブラリなので、テストの実行には実際に Kintone の API を叩く必要があります。 Kintone API を叩くには、実際にそのアプリが存在していないといけないし、アクセスするための APIトークンが必要になってきます。 例えテストプログラムといえども、この固有値をプログラムの中には埋め込みたくないですよね。 そこで、固有値に関しては、Google Apps Script の Script properties に保存するようにしています。

Script properties の値は Google Apps Script からアクセスできるクラスが準備されているので簡単に取り出すことができます。

まとめ

UUUM の社員旅行(任意参加)では石垣島に行けます!!

f:id:masatobito:20170623173301j:plain

dockerあれこれ

Dockerを使う上で、少し解りずらいけど理解したらDockerと少し親しくなれるようなところをピックアップしてみようと思います。

Docker imageって何?

設定ファイルとファイルシステムのdiffから構成される

例えば下記のようなシンプルなDockerfileからビルドしたイメージを docker inspect [image] で中身を一部抜粋すると

FROM alpine
RUN apk --no-cache add  git
CMD ["/bin/echo", "hoge"]
[
    {
        "Id": "sha256:167ac9be494e05feadc5096832622cf9b5b70acaa62ac6e17f61de624e6eb2fb",
        "RepoTags": [
            "sample1:latest"
        ],
        "RepoDigests": [],
        "Parent": "sha256:efcaf1953f1fe27f5e9372fae521da68a506da7352ce8d17f3c94a09fdaa5ec4",
         ...
        "DockerVersion": "17.03.1-ce",
        "Author": "",
        "Config": {
            ...
            "Cmd": [
                "/bin/echo",
                "hoge"
            ],
            ...
        },
        ...
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:3fb66f713c9fa9debcdaa58bb9858bd04c17350d9614b7a250ec0ee527319e59",
                "sha256:bfd51f3d31f5ed500e02975f8207a2f1f604ecd85126da84614fbe93e8d7aed8"
            ]
        }
    }
]

こんな感じになります。

ちなみに alpine はとてもサイズが小さいLinux OSです。

alpinelinux.org

CMDで指定した内容が Config の Cmd にあって、ファイルシステムのdiff(RootFS Layers)に、alpineの分と、apk で gitをインストールした内容の2つ分入っていることが解ります。

イメージのレイヤーを確認する docker history [image] を実行すると

167ac9be494e        6 minutes ago       /bin/sh -c #(nop)  CMD ["/bin/echo" "hoge"]     0 B                 
efcaf1953f1f        23 hours ago        /bin/sh -c apk --no-cache add  git              21.5 MB             
a41a7446062d        11 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0 B                 
<missing>           11 days ago         /bin/sh -c #(nop) ADD file:ce33aabbc5f370e...   3.97 MB

inspectの Parent のところが historyの1つ前のイメージ efcaf1953f1f と一致していますね。 これで何となくイメージの内容が解るようになりましたね。

Copy On Write

次はファイルシステムの diff はイメージ間で共有されるというところを見ていきます。 さっきのinspectの内容の

"Layers": [
   "sha256:3fb66f713c9fa9debcdaa58bb9858bd04c17350d9614b7a250ec0ee527319e59",
   "sha256:bfd51f3d31f5ed500e02975f8207a2f1f604ecd85126da84614fbe93e8d7aed8"
]

の部分です。

apk で git をインストールしていた箇所を、ADDでファイルをコンテナに追加する処理に書き換えて試してみます

FROM alpine
ADD hoge.txt huga.txt
CMD ["/bin/echo", "hoge"]

で build して inspect した結果

"Layers": [
  "sha256:3fb66f713c9fa9debcdaa58bb9858bd04c17350d9614b7a250ec0ee527319e59",
  "sha256:77b5680dd209091028af983ee403527c3ff6d45933599e6e6d7caecfca4c7bcd"
]

1つの3fb66f … が同じで、2つめの値は違うということが見ると解りますね。 ということで、1つめの diffのハッシュが alpine のもので、2つめのハッシュはそれぞれ、apk add git の内容と、 hoget.txt の内容ということが解ると思います。

これでファイルシステムの内容が共有されるということが解りましたね。

build時のキャッシュについて

親イメージから辿っていき、命令文が完全に一致するものがあれば、新しくイメージを作らずにそれを使う

build時に既存のイメージを流用できるようなケースは、新しいイメージを作らずに既存のイメージが流用されます。

例えばさっきの1つめのDockerfileを最初にビルドすると

Sending build context to Docker daemon 99.33 kB
Step 1/3 : FROM alpine
 ---> a41a7446062d
Step 2/3 : RUN apk --no-cache add  git
 ---> Running in 0b1a3015df35
fetch http://dl-cdn.alpinelinux.org/alpine/v3.6/main/x86_64/APKINDEX.tar.gz

...

OK: 25 MiB in 17 packages
 ---> efe320ae020a
Removing intermediate container 0b1a3015df35
Step 3/3 : CMD /bin/echo hoge
 ---> Running in f0829ddf04a5
 ---> 1313f4ef40dd
Removing intermediate container f0829ddf04a5
Successfully built 1313f4ef40dd

で、Dockerfileを変更せずに再度ビルドすると

Sending build context to Docker daemon 99.33 kB
Step 1/3 : FROM alpine
 ---> a41a7446062d
Step 2/3 : RUN apk --no-cache add  git
 ---> Using cache
 ---> efe320ae020a
Step 3/3 : CMD /bin/echo hoge
 ---> Using cache
 ---> 1313f4ef40dd
Successfully built 1313f4ef40dd

となり、Using cache と出ていて、イメージのハッシュ値も一致していることが解ります。 このようにハッシュが使用されるルールは下記です。

  1. 親イメージから子に向かって処理は進められ、対象レイヤーの親イメージが持つ子イメージの中で、コマンドの内容が完全に一致しているものがあればそれが使用されます。(関係ないスペースが入ってもキャッシュは利用されなくなります)

  2. ADD, COPY 命令の場合は、ファイルの中身のチェックサムが一致するとキャッシュが利用されます。

なので、ADD, COPY命令の場合は、Dockerfile上の命令は変わっていなくても、ファイルの内容が更新された場合はキャッシュは使用されずに新しくイメージを作ってくれます。

CMD, ENTRYPOINT について

CMDとENTRYPOINTって解りにくいと思うので説明したいと思います。

2つの形式

まず書式としては2つあります

  1. shell方式 echo hoge
  2. exec方式 ["/bin/echo", "hoge"]

これらは同じ、hoge と出力するものです。 これらの方式とCMD, ENTYPOINTの挙動の違いが少し複雑です

CMDについて

イメージ対してゆるいデフォルトコマンドを指定する

  • run時に実行される
  • 上書き可能
ENTRYPOINTについて

イメージ起動時のコマンドを指定する

  • 同じようにRUN時に実行される
  • CMDと違って上書きはできない

ちょっとわかりづらくて説明もよく解らないので、実際の動きを見てみると

FROM alpine
RUN apk --no-cache add  git
CMD ["/bin/echo", "bbb"]
ENTRYPOINT ["/bin/echo", "aaa"]

を build して run すると、

aaa /bin/echo bbb

となります。 この挙動を見ると、ENTRYPOINTがあると、そっちのほうが優先され、CMDのexec形式で書いたものは引数として渡されているのが解ると思います

run時に ccc を引数として渡すと

aaa ccc

となり、CMDで指定したものが上書きされます。

次にENTYPOINTの書式をshell形式にして試してみます。

FROM alpine
RUN apk --no-cache add  git
CMD ["/bin/echo", "bbb"]
ENTRYPOINT echo aaa

これで run すると、引数有り無しにかかわらず、aaa が表示されるだけになり、CMDの内容も run の引数も影響がなくなってしまいます。 若干解りづらいですが、このあたりの挙動を理解しておくと、Dockerfileを書くときのストレスが減少できると思います。

UUUMのシステム開発ではDockerの恩恵をうけた快適な開発スタイルを実践しています^^

参考にさせて頂いたサイト