mmag

ハマったことメモなど

minikubeを触った

ふとkubernetesを触ってみようと思い、ローカルで試すにはminikubeがよいということで試してみた。一通りチュートリアルをやってわかった気になったので、ちょっとした構成をつくってみたのがこちら。

github.com

docker hubに上げてないイメージを使うために

$ minikube start
$ eval $(minikube docker-env)

とかしないといけないはず。これをした上で、

$ ./build.sh

とすると、docker buildやらDeploymentの作成が行われ、以下が2組立ち上がる。

  • 80番を3000番にリバースプロキシするnginx
  • 今の時刻を文字で返すWebアプリ
$ ./build.sh
Sending build context to Docker daemon  5.718MB
Step 1 : FROM golang:alpine
 ---> c82f63bb2928
Step 2 : ADD . /tmp
 ---> Using cache
 ---> be2da95d39d6
Step 3 : WORKDIR /tmp
 ---> Using cache
 ---> 153c17f1ff46
Step 4 : RUN go build -o /opt/app app.go
 ---> Using cache
 ---> d70e89cd9650
Step 5 : ENTRYPOINT /opt/app
 ---> Using cache
 ---> f48256763296
Successfully built f48256763296
Sending build context to Docker daemon  3.584kB
Step 1 : FROM nginx:latest
 ---> 5766334bdaa0
Step 2 : COPY ./nginx.conf /etc/nginx/
 ---> Using cache
 ---> d71e8a6ed75d
Successfully built d71e8a6ed75d
deployment "current-time" created
service "current-time" exposed
open 192.168.99.100:31744

最後に出力されているURLへ行くと、"2017-04-23 08:11:50.512403779 +0000 UTC"という感じが見えて、動いていることが確認できる。

asdf-nodejsでgpgのエラー

最近macをセットアップする機会があったので、各言語のバージョン管理にasdfというやつを使い始めました。以前も一度試したのですが、なんだったか途中でコケてよくわからんから、まぁanyenvでいいか、となっていたので再挑戦。今度はそれなりにサクッと行きそうですが、1ヶ所つまずいたとこがあったので書いておきます。

asdf-nodejs

asdf自体のインストールはREADMEに従って行いました。まずはnodeでも入れるか、と思ってasdf-nodejsを入れたところ、インストール手順に

# Imports Node.js release team's OpenPGP keys to main keyring
bash ~/.asdf/plugins/nodejs/bin/import-release-team-keyring

というものがありました。リリースチームの鍵を入れろよ、という話ですが、そのまま素直に打ったら「鍵サーバからの受信に失敗しました: no route to host」なんていうエラーが。import-release-team-keyringの中身

gpg --keyserver pool.sks-keyservers.net --recv-keys 94AE3...

というコマンドが列挙してあるだけで、サーバに辿り着けていないのかなと思うもpingは通る。ポートとかその辺かなーと思って調べると、どうやら

hkp://p80.pool.sks-keyservers.net:80

に別の口が開いているようなので、import-release-team-keyringの中のpool.sks-keyservers.netを置換して何とか鍵が入りました。署名とかに関することでエラーとか言われると不穏ですが、とりあえずこれで。

Phoenix v1.3からのディレクトリ構成をもう一度調べる

はいこんにちは。

Phoenix v1.3.0-rc.0がリリースされました。前リリースからの大きな変更として、Phoenixプロジェクトのディレクトリ構成がガラッと変わります。新たな構成に対応したジェネレータは、phoenix.*ではなくphx.*というタスクになっています。ついで感がありますが、phoenix.serverphoenix.routesもdeprecatedになり、phx.serverphx.routesになっていくようです。

github.com

以前v1.3以降でどんな構成になるのか調べたのですが、改めて調べておきましょう。

joe-noh.hatenablog.com

phx.new

まずはv1.2以前のphoenix.newと、v1.3から入るphx.newでつくられるプロジェクト構成を比較してみます。Phoenixリポジトリをcloneして、installerディレクトリの中で以下のコマンドを発行。Phoenixのリビジョンは1.3.0-rc.0タグのついた4d608bfです。

$ mix phoenix.new app_with_phoenix_new
$ mix phx.new app_with_phx_new --dev

phoenix.new--devつけたら--dev projects must be generated inside Phoenix directoryと怒られたのですが、ディレクトリ構成が見たいだけなので--dev無しで。つくられたディレクトリをtreeで見るとこんな形式。一部省略してあります。

app_with_phoenix_new
├── README.md
├── config
├── lib
│   ├── app_with_phoenix_new
│   │   ├── endpoint.ex
│   │   └── repo.ex
│   └── app_with_phoenix_new.ex
├── mix.exs
├── priv
│   ├── gettext
│   └── repo
├── test
│   ├── channels
│   ├── controllers
│   ├── models
│   ├── support
│   ├── test_helper.exs
│   └── views
└── web
    ├── channels
    ├── controllers
    ├── gettext.ex
    ├── models
    ├── router.ex
    ├── static
    ├── templates
    ├── views
    └── web.ex
app_with_phx_new
├── README.md
├── assets
│   ├── brunch-config.js
│   ├── css
│   ├── js
│   ├── package.json
│   ├── static
│   └── vendor
├── config
├── lib
│   └── app_with_phx_new
│       ├── application.ex
│       ├── repo.ex
│       └── web
│           ├── channels
│           ├── controllers
│           ├── endpoint.ex
│           ├── gettext.ex
│           ├── router.ex
│           ├── templates
│           ├── views
│           └── web.ex
├── mix.exs
├── priv
│   ├── gettext
│   └── repo
└── test
    ├── support
    ├── test_helper.exs
    └── web
        ├── channels
        ├── controllers
        └── views

以前書いた

  • weblib配下に入る
  • modelという概念がなくなる

はそのままのようですが、加えて、これまでweb/static配下にあったcssjsや、package.jsonbrunch-config.jsがまるっとトップレベルのassetsに移っています。見慣れないapplication.exというファイルがありますが、これは上の例だとapp_with_phoenix_new.exに当たるもので、名称が変更されるようです。全体的にディレクトリ構成とモジュール名の対応関係がキチンとしていて、生成されたlib/app_with_phx_new/web/page_controller.exではAppWithPhxNew.Web.PageControllerモジュールが定義されていました。

defmodule AppWithPhxNew.Web.PageController do
  use AppWithPhxNew.Web, :controller

  def index(conn, _params) do
    render conn, "index.html"
  end
end

ただ、よく見るとweb.exが入る場所が一段深くなっていて、lib/app_with_phx_new/web/web.exとなっています。ここはモジュール名とファイル配置の対応が取れていませんが、Elixir ForumのJoséのコメントによると、意図したものであるようです。

phx.gen.json

次はphx.gen.jsonを試してみます。まずはhelpから。

$ mix help phx.gen.json

                                mix phx.gen.json

Generates controller, views, and context for an JSON resource.

    mix phx.gen.json Accounts User users name:string age:integer

The first argument is the context name followed by the schema module and its
plural name (used for resources and schema).

The above generated resource will add the following files to lib/your_app:

  • a context module in accounts.ex, serving as the API boundary to the
    resource
  • a schema in accounts/user.ex, with an accounts_users table
  • a view in web/views/user_view.ex
  • a controller in web/controllers/user_controller.ex
  • default CRUD templates in web/templates/user

As well as a migration file for the repository and test files for generated
context and controller features.

## Schema table name

By deault, the schema table name will be the plural name, namespaced by the
context name. You can customize this value by providing the --table option to
the generator.

Read the documentation for phx.gen.schema for more information on attributes
and supported options.

Location: _build/dev/lib/phoenix/ebin

これまではmix phoenix.gen.json User users name:string age:integerのように、スキーマのモジュール名とテーブル名、テーブル構造を引数に与えていましたが、Accountsという第一引数が増えています。helpにある例をそのまま実行してみます。

$ mix phx.gen.json Accounts User users name:string age:integer
* creating lib/app_with_phx_new/web/controllers/user_controller.ex
* creating lib/app_with_phx_new/web/views/user_view.ex
* creating test/web/controllers/user_controller_test.exs
* creating lib/app_with_phx_new/web/views/changeset_view.ex
* creating lib/app_with_phx_new/web/controllers/fallback_controller.ex
* creating test/accounts_test.exs
* creating lib/app_with_phx_new/accounts/user.ex
* creating priv/repo/migrations/20170302235526_create_accounts_user.exs

Add the resource to your api scope in lib/app_with_phx_new/web/router.ex:

    resources "/users", UserController, except: [:new, :edit]


Remember to update your repository by running migrations:

    $ mix ecto.migrate

引数に与えたAccountsがどこに効いているかというと、lib/app_with_phx_new/accounts/user.exのようにuserのひとつ上の名前空間になっています。テーブル名も、特に指定しないとaccounts_usersとなるようです。また、コンソールに出ていませんが、しれっとlib/app_with_phx_new/accounts/accounts.exも作られていました。このAccountsモジュールはいわゆる「境界づけられたコンテキスト」のひとつで、他のモジュール、例えばUserControllerは、Userモジュールに定義した関数を使ったり、Repo.all(User)のようなことはせず、このAccountsモジュール経由でユーザの操作を行うようにしましょうね、という意図があるようです。生成されたファイルの中を見ても、UserControllerindexアクションでは、Accounts.list_usersを使ってユーザ一覧を得るようになっていました。Userモジュールはスキーマ定義があるだけでほぼ空っぽで、テストもUserではなくAccountsモジュールのものが作られています。

ただ、生成されたAccountsモジュールの中に以下の関数が定義されており、少々やりすぎではという印象でした。Accountsモジュールが太っていきそうなので、自分ならこれは今まで通りUserモジュールに移します。

def AppWithPhxNew.Accounts do
  ...

  defp user_changeset(%User{} = user, attrs) do
    user
    |> cast(attrs, [:name, :age])
    |> validate_required([:name, :age])
  end
end
defmodule AppWithPhxNew.Accounts.User do
  use Ecto.Schema
  
  schema "accounts_users" do
    field :name, :string
    field :age, :integer

    timestamps()
  end

  # ここにchangeset/2を書きたい
end

なお、lib/app_with_phx_new/web/controllers/fallback_controller.exという馴染みのないものがありますが、ちょっとここでは深掘りしません。

まとめ

ざっくりまとめると、

  • PhoenixはElixirアプリケーションのWebフロントとして考える
  • テーブルと直接対応していないコンテキストモジュールをAPIの境界とし、ここにロジックを書く

ということをフレームワークが緩く強制するということのようです。