macOSで起動時にDockerデーモンを立ち上げる

Docker for Mac (Docker.app) を使えばHyperKitによるMacネイティブな仮想化の恩恵を受けられる。
開発用としてGUIログインする場合にはこれで十分。 f:id:S64:20170609013514p:plain


が、macOS Serverとして利用する場合はGUIログインしたくない。
現時点 (2017-06-09 01:36, Docker.app stable 17.03.1-ce-mac12) ではDocker.appはGUIログイン時にしか自動起動できず、またdocker, docker-composeなどの関連コマンドもDocker.appが自動的に配置するため、ブートタイムにこれを利用するのは難しい。

一応psすると/Applications/Docker.app/Contents/MacOS/com.docker.hyperkitあたりが実体のように見えるが、それなりに複雑なパラメータが渡されているため安易に直叩きするのは危険。


仕方ないので、Docker Toolboxに同梱されているdocker-machineを使う。すなわち、VirtualBox上のdockerマシンを見ることにする。

  • Docker Toolbox
  • VirtualBox
  • Oracle VM VirtualBox Extension Pack

をインストール。

docker-machineは各実行ユーザの~/.docker/machine配下で管理するようなので、デーモン用のユーザを作成。 今回は_myawesomeuser、ホームディレクトリを/Users/_myawesomeuserとした。(Macにおいてデーモン等のシステムユーザは先頭がアンダースコア)

システムユーザなのでログインさせないようにしておく。

sudo dscl . create /Users/_myawesomeuser IsHidden 1

以下のようにしてデーモン用ユーザになり、仮想マシンを作成。

sudo su -l _myawesomeuser
docker-machine create myawesomemachine
exit # 元のユーザに戻る

/Library/LaunchDaemons配下に、以下のような起動スクリプトを作成。

<!-- /Library/LaunchDaemons/your.awesome.domain.myawesomemachine.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
      
        <key>EnvironmentVariables</key>
        <dict>
            <key>PATH</key>
            <string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin</string>
        </dict>
        
        <key>Label</key>
        <string>your.awesome.domain.myawesomemachine</string>
        
        <key>UserName</key>
        <string>_myawesomeuser</string>
        
        <key>ProgramArguments</key>
        <array>
            <string>/usr/local/bin/docker-machine</string>
            <string>start</string>
            <string>myawesomemachine</string>
        </array>
        
        <key>RunAtLoad</key>
        <true/>
        
    </dict>
</plist>

以下のようにして起動に組み入れる。

sudo launchctl load /Library/LaunchDaemons/your.awesome.domain.myawesomemachine.plist

注意点として、Docker.appの場合と違いポートがexposeされる先がVirtualBoxの仮想マシンになる。以下のようにして仮想マシンのIPを取得し叩く必要がある。

sudo -u clean -- bash -l -c 'docker-machine ip myawesomemachine'

よってリバースプロキシなんかを挟む場合には一筋縄では行かない。これはまたこんど記事を書く。

macOSで80番ポートを開ける

macOS 10.12.4、加えてmacOS Server (Server.app) 5.3.1を想定。


まず、普通に開けようとすると以下のようにエラーになる。

$ ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 80).start'
[2017-06-09 01:03:38] INFO  WEBrick 1.3.1
[2017-06-09 01:03:38] INFO  ruby 2.3.0 (2015-12-25) [x86_64-darwin15]
/Users/shuma/.anyenv/envs/rbenv/versions/2.3.0/lib/ruby/2.3.0/socket.rb:205:in `bind': Permission denied - bind(2) for 0.0.0.0:80 (Errno::EACCES)
  from /Users/shuma/.anyenv/envs/rbenv/versions/2.3.0/lib/ruby/2.3.0/socket.rb:205:in `listen'
   from /Users/shuma/.anyenv/envs/rbenv/versions/2.3.0/lib/ruby/2.3.0/socket.rb:759:in `block in tcp_server_sockets'
...

使ってるサーバは違えど、bind時にPermission denied系統が投げられる。
これはMacのセキュリティ仕様として、1000番ポート以下を一般ユーザが開けられないため。昇格すればいける。

$ sudo ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 80).start'
Password:
[2017-06-09 01:06:25] INFO  WEBrick 1.3.1
[2017-06-09 01:06:25] INFO  ruby 2.3.0 (2015-12-25) [x86_64-darwin15]
[2017-06-09 01:06:25] INFO  WEBrick::HTTPServer#start: pid=35327 port=80

環境によっては、以下のようになる。

$ sudo ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 80).start'
Password:
[2017-06-09 01:08:05] INFO  WEBrick 1.3.1
[2017-06-09 01:08:05] INFO  ruby 2.3.0 (2015-12-25) [x86_64-darwin15]
/Users/shuma/.anyenv/envs/rbenv/versions/2.3.0/lib/ruby/2.3.0/socket.rb:205:in `bind': Address already in use - bind(2) for 0.0.0.0:80 (Errno::EADDRINUSE)
  from /Users/shuma/.anyenv/envs/rbenv/versions/2.3.0/lib/ruby/2.3.0/socket.rb:205:in `listen'
   from /Users/shuma/.anyenv/envs/rbenv/versions/2.3.0/lib/ruby/2.3.0/socket.rb:759:in `block in tcp_server_sockets'
...

Address already in use、つまり80番ポートが使用されている。
Macにはapacheがビルトインされているので、ほとんどの場合こいつのせい。以下で自動起動を辞めさせられる。

$ sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist

ただし、macOS Server (Server.app) の場合は

$ sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist
/System/Library/LaunchDaemons/org.apache.httpd.plist: Could not find specified service

なんて風になって止められない。80番ポートを自由に使いたいという前提で進めているので、apacheの80番ポートlistenを辞めさせればいい。
/Library/Server/Web/Config/apache2/httpd_server_app.confにある設定ファイルで、以下のようにコメントアウト。

#Listen 80
#Listen 443

内部では80番ポートが見えるが、外からは永遠に待たされるなど見えない場合がある。
nmapなんかで確認すると

$ nmap 127.0.0.1

Starting Nmap 7.12 ( https://nmap.org ) at 2017-06-09 01:23 JST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00074s latency).
Not shown: 722 closed ports, 268 filtered ports
PORT     STATE SERVICE
80/tcp   filtered  http
...

なんて風になっているかもしれない。これはMacのファイアウォールで許可していないため。
環境設定からファイアウォールのオプションを開き、接続許可をすれば良い。

f:id:S64:20170609012734p:plain

android-radiobutton-extendedを公開しました。

AndroidのRadioButtonを拡張するコンポーネント郡をOSSとして公開しました。

github.com

以下のコンポーネントが含まれます:

  • CompoundFrameLayout
  • RadioFrameLayout
  • RadioGroupedAdapter

CompoundFrameLayout, RadioFrameLayout

CheckableなFrameLayoutの実装です。
選択中 / 選択解除の表現が必要なレイアウトで利用するのがおすすめです。
たとえば、backgroundとしてandroid:state_checkedで表示が切り替わるdrawableを作るとよいでしょう。

RadioGroupedAdapter

RecyclerViewで単一チェックのみを許容するAdapterを表現するサンプル実装です。RadioGroupに似た挙動をするAdapterだとイメージしてください。
実体はRadioGroupedAdapter.Helperになっているため、既存のカスタムAdapterがある場合、内部のフィールドで呼び出すと上手く使えます。

Example

それぞれexampleというモジュール内にサンプル実装が存在します。
なお、exampleはPlay Store上でデモアプリを公開しています。

play.google.com

License

Apache License 2.0です。

今後の予定

  • 0.1.0なので、破壊的変更をするかも
  • 趣味ではXamarinをよくやるので、Bindingを作りたい