WCF で簡単にプロセス間通信

開発部の y-okubo です。

今回は C# の(正確には .NET Framework)WCF を使ってプロセス間通信を簡単に行ってみようと思います。

WCF の基本的な概念については以下の記事を参照してください。

www.atmarkit.co.jp

今回やりたい事を図にすると以下のような内容になります。

f:id:nekojarashi-Inc:20170814181814p:plain

WCF 関係コード

サービス実装

お行儀良くやるのであれば、インターフェイスの定義と実装は別ファイルにすべきでしょうが、ここはサクッと作ってしまいたいので単一ファイルに両方定義してしまいます。

using System.ServiceModel;

namespace Hoge
{
    [ServiceContract]
    public interface ISearchService
    {
        [OperationContract]
        void Execute();
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
    public class SearchService : ISearchService
    {
        public delegate void Searcher();

        public Searcher SercherDelegate;

        public void Execute()
        {
            SercherDelegate();    // 実装はデリゲートします
        }
    }
}

サービス起動(サーバ)

例外処理はかなりアバウトです。

SearchService service = new SearchService();
service.SercherDelegate = hogeForm.SearchRequest;    // デリゲート登録

serviceHost = new ServiceHost(
    service,
    new Uri("net.pipe://localhost/Hoge"));

try
{
    serviceHost.AddServiceEndpoint(
        typeof(ISearchService),
        new NetNamedPipeBinding(),
        "SearchService");
    serviceHost.Open();
}
catch (AddressAlreadyInUseException)
{
    MessageBox.Show("既にサービスは起動しています。");
}
catch (Exception e)
{
    MessageBox.Show(e.ToString());
}

サービス呼び出し(クライアント)

new ChannelFactory<ISearchService>(
    new NetNamedPipeBinding(),
    new EndpointAddress("net.pipe://localhost/Hoge/SearchService")).CreateChannel()
    .Execute();

サービス関連のハマり所

  • NetNamedPipeBinding() の引数はサービス側と呼び出し側で揃えないと例外が発生するので気を付ける。
  • EndpointAddress の指定を間違えないように気を付ける。
    • EndpointAddress は ServiceHost の URI と Endpoint を結合した文字列。
    • サービス側はこれらを別々に指定しているが、呼び出し側は結合した文字列を指定しているので注意。

Windows Forms 関係コード

実際にフォームを呼び出す

サービス起動時に登録したデリゲートメソッドです。

public void SearchRequest()
{
    // ここは必ず別スレッドで呼ばれる
    Invoke((MethodInvoker)delegate ()
    {
        MainForm_ShowSearchForm(this, null);
    });
}

フォーム呼び出し時のハマり所

  • メインスレッドで実行せねばならないので気を付ける。
    • 上記のメソッドはサービスのスレッド(バックグラウンド)から呼び出されることが分かっているので、InvokeRequired でのチェックを省略しています。

まとめ

WCF を使う方法について簡単に紹介いたしました。

プロセス間通信となるとかなり面倒なイメージがありますが、WCF を使えばサーバ・クライアント通信の様に行うことができます。

最近のアプリは役割毎に小さな粒度でプロセスを分けて緩やかに連携する、いわゆるマイクロサービス的な構成の物が増えているようですので、C# のみでアプリを構成するのであれば WCF は有力な選択肢になるかと思います。

WCF を使う上でこの記事がお役に立てば幸いです。

ねこじゃらしについて

ねこじゃらしでは C# に限らず Ruby (Ruby on Rails) や JavaScript での開発経験がある Web エンジニア、UI/UX デザイナを募集しております。

ご興味をお持ちいただけましたら、以下のリンクからお問い合わせください。

www.nekojarashi.com

シンプルなUIを作る

簡単なUIを作るために

開発部のiOS担当の林田です。 見た目上のUIがシンプルであっても条件が多い設計は実装に時間がかかり、文字や画像の多さ大きさなどでレイアウトが大きく変わってしまいます。 今回はスマホのUI設計をするときにシンプルで条件の少ないレイアウトを作っていきます。

このようなデザインがあったとします。

f:id:nekojarashi-Inc:20160825191953p:plain

これを要素ごと色分けをします。
ここでは各要素のマージンは8px(dx)として考えます

f:id:nekojarashi-Inc:20160825192220p:plain

要素としては

  • アイコン(黄色)
  • タイトル(緑)
  • テキスト(水色)

に分かれます。

iOSであればUIView、AndroidではViewと呼ばれる単位として分けます。 表示されるディスプレイの固定であれば問題ありませんが、スマホは端末によってサイズが異なります。

横幅の長い端末で表示するとこうなります。

f:id:nekojarashi-Inc:20160825192451p:plain

これはアイコンとタイトルの横幅が固定(Fix)になっており、背景とテキストの横幅が可変(Flexible)になっているためです。

f:id:nekojarashi-Inc:20160825192559p:plain

スマホのみならずブラウザでもよく見られるデザインで、固定値と可変値をもたせ、様々なサイズのディスプレイに対応します。

実はこのデザインには問題あります、タイトルが固定値となっているため、長いタイトルが入ると入りきれなくなります。
試しにタイトルをJectorからThe long title applicationに変更してみます。

f:id:nekojarashi-Inc:20160826154840p:plain

タイトルがテキストに被っているので、いくつか対応するレイアウトにしてみます。

  • タイトルの横幅がテキストの横幅より優先して表示される

f:id:nekojarashi-Inc:20160826155048p:plain

この場合条件は

  • タイトルの横は可変、縦は固定
  • テキストの横は可変、縦は可変
  • タイトルはテキストより先に横幅が決まる

ということになります。 非常に見難いレイアウトなっていますね。

  • タイトルは一定のサイズ以上になると後が省略される

f:id:nekojarashi-Inc:20160826155219p:plain

レイアウトは変わりませんが、もしタイトルがここでしか表示されないのであれば、ユーザーはすべてのタイトルは見えないことになります。

  • タイトルは、テキストの下に表示される

f:id:nekojarashi-Inc:20160826155330p:plain

この場合条件は

  • タイトルはテキストの下に表示される
  • タイトルの横は可変、縦は固定
  • テキストの横と縦は可変

とりあえずこれにしましょう。

ですがまだ問題があります。
先ほどタイトルはテキストの下に表示されるという条件を追加しましたが、テキストが少ない時にこうなります。

f:id:nekojarashi-Inc:20160826155452p:plain

条件を付け加えます

  • タイトルはテキストとアイコンのどちらか、縦幅の大きいほうの下に表示される
  • タイトルの横は可変、縦は固定
  • テキストの横と縦は可変

f:id:nekojarashi-Inc:20160826160023p:plain

これで正常に表示されました

ここまで変更するとタイトル上にあったほうが良いと思います

f:id:nekojarashi-Inc:20160826155917p:plain

  • タイトルの横は可変、縦は固定
  • テキストの横と縦は可変
  • アイコンの横と縦は固定

結構シンプルな条件になりました。

まとめ

  1. 要素ごと分ける
  2. 固定値と可変値を割り振る
  3. 最小値と最大値を決める
  4. 条件を付け過ぎると矛盾がおきたり、想定してなかったレイアウトになってり、実装に時間がかかったりする

お知らせ

ねこじゃらしではUI/UX デザイナを募集しております。

ご興味をお持ちいただけましたら、以下のリンクからお問い合わせください。

www.nekojarashi.com

Swift で MessagePack-RPC

ねこじゃらしの y-okubo と申します。

今回は、Swift で MessagePack-RPC を実現するライブラリの使い方を紹介します。

MessagePack-RPC については、こちらをご覧ください。

www.slideshare.net

ひとことで言えば、MessagePack (JSON) を使った RPC です。

各言語にライブラリが用意されており、異なる言語で作ったアプリ間でプロセス間通信をするのに便利です。

github.com

なぜか公式にはリストアップされていないのですが、Objective-C 版もあります(CocoaPods 対応がありがたい)。

github.com

今回は、コレを Swift から使ってみることにしました。

プロジェクトの作成

まず、Xcode で適当なプロジェクトを作成します。 今回は OS X - Application - Command Line Tool のプロジェクトを作成します。

f:id:nekojarashi-Inc:20160810182458p:plain

ターミナルでそのディレクトリまで移動して、以下のコマンドで CocoaPods をインストールします。

pod init

この後は、作成された Podfile に使用するライブラリを追加していきます。 Podfile を編集して、以下の内容を追加します。

pod 'MPMessagePack'

以下のコマンドを実行してライブラリをインストールします。

pod install

CocoaPods のライブラリを Xcode で使うには、先のコマンドで生成された .xcworkspace ファイルを開きます。

サンプルコード

簡単なコードを書いて動作確認をしてみます。

まずはサーバのリクエストハンドラを追加します。

RPC が呼び出されると必ずこのコードが実行されます。

メソッド名で分岐することで RPC 呼出しに対応したコードを記述できます。

今回は実験的なコードなので、そのような分岐はせず同じような戻り値を返しています。

func serverRequestHandler(messageId: NSNumber?, method: String?, params: [AnyObject]?, completion: (MPRequestCompletion)?) {
    if let method = method, let params = params {
        print("(Server) method:", method)
        print("(Server) params:", params)
    }
    
    completion!(nil, "Return from server(\(method))")
}

サーバを起動するコードを追加します。

リクエストハンドラを設定して、ポート 5001 番でサーバを起動してみます。

// Server

var server: MPMessagePackServer = MPMessagePackServer()

server.requestHandler = serverRequestHandler

do {
    try server.openWithPort(5001)
}
catch let error as NSError {
    print(error)
}

クライアントのコードを追加します。

こちらは非同期実行のコードになります。

var client: MPMessagePackClient = MPMessagePackClient()

client.openWithHost("127.0.0.1", port: 5001, completion: {error in
    client.sendRequestWithMethod("method_hoge", params: ["hoge", "42"], messageId: 1, completion: {error, result in
        if let error = error {
            print("Error:", error)
        }
    })
})

同期実行のコードは以下になります。 こちらは Exception を throw するので do ~ try ~ catch で例外ハンドリングをする必要があります。

client.openWithHost("127.0.0.1", port: 5001, completion: {error in
    do {
        try print(client.sendRequestWithMethod("method_fuga", params: [], messageId: 1, timeout: 2.0))
    } catch let error as NSError {
        print("Error:", error)
    }
})

注意点として、Command Line Tool でプロジェクトを作成している場合は、以下のコードも追加してください(このコードを追加しないと動きません)。

このコードで無限ループを回してクライアントからのリクエストを待ちます。

NSRunLoop.currentRunLoop().run()

では、コードを実行してみます。

2016-08-12 15:35:40.222 MsgpackRpcSwiftExample[11273:1429215] -[MPMessagePackServer connectionWithInputStream:outputStream:]:38: [Server] Client connected
(Server) method: method_fuga
(Server) params: []
(Server) method: method_hoge
(Server) params: [hoge, 42]

クライアントからの RPC 呼出しに対してサーバが値を受け取っているのがおわかりいただけるかと思います。

まとめ

MessagePack-RPC を Swift から使う方法について簡単に紹介いたしました。

他の言語についてもライブラリが存在するので、異なる言語間でプロセス間通信を行うような処理が必要になったとき、気軽に使えて便利です。

最近はマイクロサービス化によりコンポーネント疎結合化が加速しており、設計の都合やチームのスキルセットによって言語を使い分けるシーンも増えているのではないでしょうか。

そのようなときに、それらコンポーネントを繋ぐ方法として MessagePack-RPC は有力な選択肢になり得ると思います。

MessagePack-RPC を使う上でこの記事がお役に立てば幸いです。

お知らせ

ねこじゃらしでは Swift に限らず RubyJavaScriptプログラマ、UI/UX デザイナを募集しております。

ご興味をお持ちいただけましたら、以下のリンクからお問い合わせください。

www.nekojarashi.com

Go の パッケージ管理ツールは glide がイイ感じ

ねこじゃらしの y-okubo と申します。

今回は、仕事で Go を書いたときに使ったパッケージ管理ツール glide について簡単にですが、紹介させていただきます。

そもそも Go にはパッケージ管理を行うための仕組みがありません。
パッケージ管理をし易くするための仕組みが、現行の 1.6 で導入された Vendoring です。

Vendoring の詳しい説明はググっていただくとして、実際にはパッケージ管理を行うためのツールが必要になります。

この手のいわゆるパッケージ管理ツールは gbgom が有名のようですが、今回は試してみて便利だった glide を採用することにしました。

glide 使い方

glide コマンド自体をインストールします。

$ go get github.com/Masterminds/glide
$ go install github.com/Masterminds/glide

glide コマンドで依存パッケージをインストールします。

$ glide create
$ glide install

これで、対象ディレクトリに glide.yaml と glide.lock ができます。
また、vendor ディレクトリも作成され、その中に依存するパッケージが格納されます。
glide.yaml と glide.lock だけをリポジトリに格納して、vendor ディレクトリは Git で Ignore 指定してください。

このリポジトリを初めて使う方は、glide コマンド自体をインストールした後に glide install をすれば、vendor ディレクトリに依存パッケージがインストールされます。

まとめ

私は普段 Ruby で開発をしていて Bundler を使っているのですが、 コマンド体系や構成ファイルがそれと似ており、違和感なく使うことができました。

もう一つの候補であった gom と比べると、コマンド体系や YAML 書式がスッキリしているのもイイ感じで、ドキュメントがよく整備されているのもあって、glide を正式導入することにしました。

逆に言えば、gom は Bundler によく似たコマンド体系や YAML 書式を持っているので、かなり複雑な設定もできそうです(Go 標準のツールがシンプルで強力なので複雑な設定自体が不要な気もしますが)。

glide や gom 以外のパッケージ管理を選択されるのも良いかと思いますが、現時点では glide が個人的に最もオススメできるパッケージ管理ツールです。

glide で快適な Go 開発を!

お知らせ

ねこじゃらしでは Go に限らず RubyJavaScriptプログラマ、UI/UX デザイナを募集しております。

ご興味をお持ちいただけましたら、以下のリンクからお問い合わせください。

www.nekojarashi.com

開発部LTを開催しました! (テーマ Rack について)

バックアップ開発部の r-fujiwara です。 ねこじゃらし開発部では、二週間に一度、メンバーの最近の興味を持っていることや、業務で行っていることなどを持ち回りで LT を行っています。

今回

今回は記事を書いている r-fujiwara の番でした。 主に Rack(Application / Server) の話をさせて頂きました。

分かったつもりになるかもしれないRack

speakerdeck.com

この発表をしようと考えた動機

  • (冒頭にもありますが、)教えている後輩に「Rack って何ですか?」と聞かれて答えに窮してしまったので、この機会にちょっと触れてみたかったから。

  • RailsDevelopment で開発していると、やはり「WEBrickって何よ?」「Puma って何よ?」って所に関して、多分自分自身もあまり自信を持って答えられ無さそうだったので、この機会で整理したかったから。

  • しかしながら、Rack のことをあまりにも普通に話しすぎてしまっても、それはそれで何かしらのドキュメントを読めばいい話であると考え、今回は「車で例える」というアプローチを取り、より噛み砕いた説明が出来ないか、と考えて今回の Rack の発表といたしました。

こういった背景があります。

個人的感想

  • 自分としては rack は非常に面白い仕組みであり、Rack Server や Rack Application のソースを読むだけでもかなり力になる、と考えております。

  • アプリケーションを作るだけでなく、その中身がどう動いているのか?とちゃんと理解して然るべき技術選定出来るエンジニアになっていきたいと私は考えておりますし、またそういった人材をねこじゃらしで育てていければ、と考えております。

Go の標準っぽいプレゼンを作る - 完全版

バックアップ開発部の y-okubo と申します。

今回は、以下の記事で書いた内容の(ほぼ)完全版を書きます。

qiita.com

概要

  • Go 界隈の方々がよく使っているプレゼンツール(フォーマット)です
    • とは言えど、このツールを使っていないプレゼンもいっぱいあります
  • 当然ですが Go で書かれています
  • シンタックスハイライトは美しいですが、フォーマットの指定方法が特殊です

インストール

GOPATH を設定した上でインストールします

$ go get golang.org/x/tools/cmd/present

サーバ起動

作成するスライドを格納するディレクトリに移動してから実行します

$ present

デフォルトでは http://127.0.0.1:3999 にアクセスすることでプレゼンが見られるようになります。

フォーマット

プレゼンのメタ情報

Title of document
Subtitle of document
15:04 2 Jan 2016
Tags: foo, bar, baz

Author Name
Job title, Company
joe@example.com
http://url/
@twitter_name

* Title of slide or section (must have asterisk)

最低限、この内容(アスタリスクの部分)までないと表示できません(以外とハマります、コレ)。

  • 一行目はプレゼンのタイトルです
  • 二行目はプレゼンのサブタイトルです
  • 三行目はプレゼンの発表日時?
  • 四行目はタグ?ですかね(どこで役に立つのかは不明)
  • 空行を挟んで個人プロフィール各種(名前・役職・会社名・メールアドレス等々)
  • さらに空行を挟んで次のスライドタイトルになります

箇条書きとセクション

- bullets 1
- more bullets 1
- a bullet with 1
* Slide title or section
** Sub section
*** More sub section

サブセクション以降はプレゼンの下から順に表示されます。

例えば

* Title of slide or section (must have asterisk)

** Subsection1

- bullets 2
- more bullets 2
- a bullet with 2

*** Subsubsection2

- bullets 1
- more bullets 1
- a bullet with 1

これは

f:id:nekojarashi-Inc:20160531123924p:plain

こんな感じで表示されます(正直ちょっと違和感があります)。

テキスト

* Text

Some More text

  Preformatted text
  is indented (however you like)

f:id:nekojarashi-Inc:20160530120910p:plain

書いた内容がそのまま反映されます。

インデントを行うとその部分が整形済みテキスト(pre タグ)になります。

フォント

* Font

_italic_
*bold*
`program`
_this_is_all_italic_
_Why_use_scoped__ptr_? Use plain ***ptr* instead.

上から順番に * イタリック * ボールド * プログラム? * イタリック * ボールド(先頭のアスタリスクもボールドにしたいのでエスケープしてる?)

こんな感じで表示されます。

f:id:nekojarashi-Inc:20160531124228p:plain

インラインリン

* Inline link

[[url][label]], or [[url]]

f:id:nekojarashi-Inc:20160530121337p:plain

ラベルを付けるか、そのままリンクを表示するかの違いですね。

リンク

* Link

.link http://golang.org golang.org

f:id:nekojarashi-Inc:20160530121355p:plain

Playground で直接実行可能な状態にする

* The Go Playground

.play demo.go

こんな UI が付きます。

f:id:nekojarashi-Inc:20160531124021p:plain

コード

コードは別ファイルの置く必要があります。

* Code

.code -numbers test.go /START OMIT/,/END OMIT/

/開始箇所/,/終了箇所/ で表示するコードを指定します。 なお、正規表現が使えます。

イメージ

* Images

.image gopher.png 408 300

高さと幅を指定できます。

f:id:nekojarashi-Inc:20160531124632p:plain

公開する

http://go-talks.appspot.com で 動的に表示できます。

http://go-talks.appspot.com/github.com/owner/project/file.ext

という形式で指定すれば、作成したプレゼンが表示されます。

GitHub URL 途中の blobmaster は 取り除いてください。

こちらは表示例です。

http://go-talks.appspot.com/github.com/y-okubo/go-present-example/sample.slide

まとめ

いかがでしょうか。

かなりクセのあるフォーマットで書きやすいとは言えないのですが、お、できるな!と思わせるプレゼンが作れるので、 Go のコミュニティで発表する予定の資料は present で、それ以外では普段お使いのプレゼンソフトを使われのが良いかと思います。

さらに詳細なフォーマットについては present の GoDoc をご覧ください。

godoc.org

お知らせ

ねこじゃらしでは Go に限らず RubyJavaScriptプログラマ、UI/UX デザイナを募集しております。

ご興味をお持ちいただけましたら、以下のリンクからお問い合わせください。

www.nekojarashi.com

GlusterFS + Go の開発環境を Docker で構築

バックアップ開発部の y-okubo と申します。

今回は「gogfapi を使った簡単な API Server」を作るにあたり、Docker を使って Go の開発環境を構築した手順を紹介させていただきます。

gogfapi を使った〜」は弊社が毎年行っている開発合宿のお題として選んだ物で(社内的にはもうちょっとクールなお題にしています)、そちらは別の機会に紹介したいと思います。

構成

開発環境なのでデータの永続化は考慮していません。

f:id:nekojarashi-Inc:20160523170935p:plain

サーバ

当初は DockerHub でイメージを探したのですが、イメージを生成すると GlusterFS のインストールで失敗する事が多いので自作することにしました。

Dockerfile はこんな感じです(途中の root:password はとても意識が低いので適宜書き換えてご利用ください)。

FROM centos

ENV container docker

# Install require packages
RUN yum --setopt=tsflags=nodocs -y update
RUN yum --setopt=tsflags=nodocs -y install wget nfs-utils openssh-server vim
RUN yum -y swap -- remove fakesystemd -- install systemd systemd-libs

# Added to enable systemd. Reference: http://developerblog.redhat.com/2014/05/05/running-systemd-within-docker-container/
RUN yum -y update; yum clean all; \
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;

# Add epel repository
RUN wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-6.noarch.rpm
RUN rpm -ivh epel-release-7-6.noarch.rpm

# Install GlusterFS packages
RUN wget http://download.gluster.org/pub/gluster/glusterfs/3.7/LATEST/EPEL.repo/glusterfs-epel.repo -O /etc/yum.repos.d/glusterfs-epel.repo
RUN yum -y update
RUN yum --setopt=tsflags=nodocs --enablerepo=epel -y install glusterfs glusterfs-server glusterfs-fuse glusterfs-geo-replication glusterfs-cli glusterfs-api glusterfs-api-devel glusterfs-devel
RUN yum --setopt=tsflags=nodocs --enablerepo=epel -y install attr iputils iproute
RUN yum clean all

RUN echo 'root:password' | chpasswd
VOLUME [ “/sys/fs/cgroup” ]

EXPOSE 111 245 443 24007 2049 8080 6010 6011 6012 38465 38466 38468 38469 49152 49153 49154 49156 49157 49158 49159 49160 49161 49162

RUN systemctl disable nfs-server.service
RUN systemctl enable rpcbind.service
RUN systemctl enable sshd.service
RUN systemctl enable glusterd.service
CMD ["/usr/sbin/init"]

COPY ./startup.sh /var/startup.sh
RUN chmod +x /var/startup.sh
RUN echo /var/startup.sh >> /root/.bashrc

ポイントは

  • EPEL レポジトリを追加
  • startup.sh を実行

しているところでしょうか。

GlusterFS のインストールに失敗していたのが yum install あたりだったので、自分で EPEL レポジトリを追加しています。

startup.sh の内容についてはこの後に説明します。

その他の内容は GlusterFS official docker image を参考にしています。

startup.sh の内容は以下になります。

#!/bin/bash

# Create volume
mkdir /var/gfs_srv_vol
gluster peer probe $HOSTNAME
gluster volume create gfs_volume $HOSTNAME:/var/gfs_srv_vol force
gluster volume start gfs_volume
gluster volume quota gfs_volume enable

# Mount volume
mkdir /var/gfs_cli_vol
mount -t glusterfs -o acl $HOSTNAME:/gfs_volume /var/gfs_cli_vol

こちらでは GlusterFS のボリューム作成とローカルマウントを行っています。

Dockerfile の中の RUN で実行したかったのですが、$HOSTNAME 環境変数を使いたかったのでシェルスクリプトで実行するようにしてあります。

クライアント側

サーバ側とほぼ一緒の Dockerfile になります(こちらも途中の root:password はとても意識が低いので適宜書き換えてご利用ください)。

FROM centos

ENV container docker

# Install require packages
RUN yum --setopt=tsflags=nodocs -y update
RUN yum --setopt=tsflags=nodocs -y install wget nfs-utils openssh-server vim
RUN yum -y swap -- remove fakesystemd -- install systemd systemd-libs

# Added to enable systemd. Reference: http://developerblog.redhat.com/2014/05/05/running-systemd-within-docker-container/
RUN yum -y update; yum clean all; \
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;

# Add epel repository
RUN wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-6.noarch.rpm
RUN rpm -ivh epel-release-7-6.noarch.rpm

# Install GlusterFS packages
RUN wget http://download.gluster.org/pub/gluster/glusterfs/3.7/LATEST/EPEL.repo/glusterfs-epel.repo -O /etc/yum.repos.d/glusterfs-epel.repo
RUN yum -y update
RUN yum --setopt=tsflags=nodocs --enablerepo=epel -y install glusterfs glusterfs-fuse glusterfs-cli glusterfs-api glusterfs-api-devel glusterfs-devel
RUN yum --setopt=tsflags=nodocs --enablerepo=epel -y install attr iputils iproute
RUN yum clean all

RUN echo 'root:password' | chpasswd
VOLUME [ “/sys/fs/cgroup” ]

EXPOSE 111 245 443 24007 2049 8080 6010 6011 6012 38465 38466 38468 38469 49152 49153 49154 49156 49157 49158 49159 49160 49161 49162

RUN systemctl disable nfs-server.service
RUN systemctl enable rpcbind.service
RUN systemctl enable sshd.service
CMD ["/usr/sbin/init"]

RUN yum --setopt=tsflags=nodocs -y groupinstall "Development Tools"
RUN wget https://storage.googleapis.com/golang/go1.6.2.linux-amd64.tar.gz
RUN tar -C /usr/local -xzf go1.6.2.linux-amd64.tar.gz

COPY ./hello.c /root/hello.c

RUN mkdir -p /root/go/bin \
  && echo 'export GOROOT=/usr/local/go' >> /root/.bashrc \
  && echo 'export GOPATH=/go/path' >> /root/.bashrc \
  && echo 'export GOBIN=/go/bin' >> /root/.bashrc \
  && echo 'export PATH=$PATH:$GOROOT/bin:$GOBIN' >> /root/.bashrc

こちらは最後の方で Go 開発環境のインストールと設定を行っています。

Go のソースコードはローカルマシン側に置いて、コンテナ起動時にマウントして参照できるようにしています。

イメージ生成

サーバ側

$ docker build -t glusterfs-server server

クライアント側

$ docker build -t glusterfs-client client

コンテナ起動

サーバ側

$ docker run --privileged -tid -p 20022:22 --hostname gfserver --name gfserver glusterfs-server

--privileged で権限を与えて SELinux 等の影響を受けないようにしています。

クライアント側

$ docker run --privileged -tid -p 20023:22 -p 8080:8080 -v /Users/y-okubo:/go/path --link gfserver:server --hostname gfclient --name gfclient glusterfs-client

--link で先ほど起動したコンテナを参照できるようにしてあります。

コンテナへ SSH ログイン

サーバ側

$ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 20022 root@localhost

クライアント側

$ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 20023 root@localhost

セキュリティ上よろしくないのですが、毎回 OpenSSH の警告が出るのを抑止しています。

クライアント→サーバの疎通確認(libgfapi 経由でのアクセス)

クライアント側に SSH ログインして、イメージ作成時に用意(Dockerfile にて指定)した hello.c をビルド・実行します。

hello.c のソースは以下になります。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glusterfs/api/glfs.h>

int main (int argc, char** argv) {
    const char *gfserver = "server";
    const char *gfvol = "gfs_volume";

    int ret;
    glfs_t *fs;
    glfs_fd_t *fd;

    fs = glfs_new(gfvol);   // Virtual filesystem type struct
    glfs_set_volfile_server (fs, "tcp", gfserver, 0);
    ret = glfs_init (fs);
    if (ret) {
        printf( "Failed to connect server/volume: %s/%s\n", gfserver, gfvol );
        exit(ret);
    }

    char *greet = "Hello, Gluster!\n";
    fd = glfs_creat(fs, "greeting.txt", O_RDWR, 0644);
    glfs_write(fd, greet, strlen(greet), 0);
    glfs_close(fd);
    return 0;
}
$ gcc hello.c -lgfapi
$ ./a.out

まとめ

今回は GlusterFS の Go での開発環境を Docker で構築した際の流れを駆け足でご紹介しました。

今回はサーバ側でローカルマウントするようにしていますが、クライアント側でサーバ側のボリュームを FUSE マウントすれば別の用途でも使えるのではないかと考えています。

GlusterFS + Go という組み合わせにどれくらいの需要があるかは分かりませんが、何かのお役に立てれば幸いです。

お知らせ

ねこじゃらしでは Go に限らず RubyJavaScriptプログラマ、UI/UX デザイナを募集しております。

ご興味をお持ちいただけましたら、以下のリンクからお問い合わせください。

www.nekojarashi.com