GIG

赴くままに技術を。

SwaggerでWeb APIを作る - 非同期実行 (その1)

これまでは同期処理(リクエストを送ると処理が実行され、レスポンスが返答されるまで待つ処理)であったが、処理が長いものなどは非同期で処理を実行しなくてはならない。PythonではCeleryというライブラリで実現できる。 ここではまず環境構築(Flask, Celeryの連携確認)まで行う。

Celeryとは

  • 非同期のタスク/ジョブキュー管理が可能
  • 複数のBrokerをサポート
    • RabbitMQが推奨
  • Result Backendをしているするとタスクのステータスや実行結果を取得可能
  • OpenStackでも採用実績あり

インストール

前提の環境としては、CentOS7を用いる。これをVirtualBox+Vagrantで環境から用意する。

$ vagrant init centos/7
$ cp Vagrantfile{,.bak}
$ vim Vagrantfile
...(省略)...
config.vm.network "forwarded_port", guest: 5672, host: 5672
 config.vm.network "forwarded_port", guest: 15672, host: 15672
...(省略)...
$ vagrant up
$ vagrant ssh
  • rootパスワードはvagrantが初期値となっている
$ su -
Password: 
# rpm -Uvh http://download.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-9.noarch.rpm'
  • RabbitMQのインストールおよび自動起動設定を行う
# yum -y install rabbitmq-server
# systemctl enable rabbitmq-server.service
# systemctl list-unit-files -t service | grep rabbit
rabbitmq-server.service                       enabled
  • Pythonのインストール
    • Pythonのバージョンを切り替えやすいため、anyenv(pyenv)で入れておく
$ sudo yum -y install git zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel xz xz-devel
$ git clone https://github.com/riywo/anyenv ~/.anyenv
$ echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bash_profile 
$ echo 'eval "$(anyenv init -)"' >> ~/.bash_profile 
$ exec $SHELL -l
$ anyenv install pyenv
$ exec $SHELL -l
$ pyenv install 3.5.4
$ pyenv versions
* system (set by /home/vagrant/.anyenv/envs/pyenv/version)
  3.5.4
$ pyenv global 3.5.4
$ pyenv versions
  system
* 3.5.4 (set by /home/vagrant/.anyenv/envs/pyenv/version)
  • Celery, flaskのインストール
$ pip install celery flask
  • RabbitMQの設定
  • ユーザ, バーチャルホスト, ユーザタグ, 権限を設定
  • 権限は、設定、読み書き全ての権限を付与
# rabbitmqctl add_user vagrant vagrant
Creating user "vagrant" ...
...done.
# rabbitmqctl add_vhost vagrant
Creating vhost "vagrant" ...
...done.
# rabbitmqctl set_user_tags vagrant vagrant_tag
Setting tags for user "vagrant" to [vagrant_tag] ...
...done.
# rabbitmqctl set_permissions -p vagrant vagrant ".*" ".*" ".*"
Setting permissions for user "vagrant" in vhost "vagrant" ...
...done.

FlaskからCeleryを使う

以下の順番で公式チュートリアルを読んでから手を動かし始めた

  1. First Steps with Celery
  2. Celery Based Background Tasks

ほとんどチュートリアル通りの内容。

celery_flask.py

config.py

app.py

次にこれを実行するが、その前にCeleryワーカーを起動していないくてはならない。celery workerコマンドを使うとフォアグラウンドで実行するので、celery multiコマンドを使って、バックグラウンドで実行する

$ sudo mkdir -p /var/run/celery
$ sudo mkdir -p /var/log/celery
$ sudo chown -R vagrant. /var/run/celery/
$ sudo chown -R vagrant. /var/log/celery/
$ celery multi start w1 -A app -l info --pidfile=/var/run/celery/%n.pid --logfile=/var/log/celery/%n%I.log

最後にターミナルをもう1つ開いてタスクが投げ込まれるかを見てみる。

  • ターミナル1
$ python
Python 3.5.4 (default, Aug 24 2017, 12:02:53)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from app import add_together
>>> add_together.delay(1,3)
<AsyncResult: 0ee3b370-561c-4e9f-a978-58635b00df89>
  • ターミナル2
$ tailf /var/log/celery/w1-1.log
...(省略)...
[2017-09-16 02:24:23,203: INFO/ForkPoolWorker-1] Task app.add_together[74dabaad-aa7e-4dee-aab1-a505deaeb76f] succeeded in 0.0009727960004966008s: 4

押して開く

半年ぐらいずっと同じような指摘をいただいている。

「会になっていない」
「口割りまで降りていない」

だったり、矢が下に行ったり。

弓道で本当に一連の動作が大事だなと思えるのは、こういった原因が直前の動作の引き分けにあるのではなく、遡ると取り懸けに原因があったり、足踏みにあったり、奥が深い。

まだ何が原因かは突き止めていないが、先日の練習でイメージが湧いたのは、「妻手を肩にのせるように」というアドバイスだった。 意識すると妻手の肩が入っておらず、それが先の指摘に続いていた。そうすると今度は小さく引いているような気がしてくる(右肩がクルッと回っているような感覚)。これでは次はおそらく大きく引くことという指摘をもらうだろう。

そうなると今の時代は便利で、Youtubeで人の行射を眺めたり、解説を読んだり。最後にたどり着いたのは「押して開くこと」。しばらくはこの押して開くを無意識にできるよう、なんどもなんども反復したい。

SwaggerでWeb APIを作る - DBと連携する

前の記事で書いたWeb APIを今度はDBと連携させる。前回までは辞書オブジェクトにデータを保存していたので、アプリケーションを再起動させるとPOSTしたデータが失われることになる。

DBとしてSQLiteを使ってみる。 業務では専らPostgreSQLなんだけど、開発時や組み込みで使われることがある(らしい)。

まずはdb.pyを作り、下記のような接続処理を書く。

petshop/db.py

create_engineの引数では、DBの接続パスと、文字列をunicode文字列として扱うようにcovert_unicode=Trueを指定する。

次にモデルを定義する。モデルで定義した属性は、そのままDBの属性となる(O/Rマッピング機能)。 Petモデルを以下のように定義する。

petshop/model.py

as_dict()は、検索系のAPIで辞書データを返す必要があるため、そのようなメソッドを用意している。 モデルまで作成したので、モデルからDBを初期化する。

$ python -c "from db import init_db; init_db()"
$ cat petstore.db 
nn??tablepetspetsCREATE TABLE pets (
    id INTEGER NOT NULL, 
    name VARCHAR(100), 
    tag VARCHAR(20), 
    created DATETIME, 
    PRIMARY KEY (id)

コントローラー部分は、DB連携のためPetモデルの処理とdb_sessionの操作を追加している。

petstore/default_controller.py

一覧取得find_petsは、複数のタグによる検索が可能となっているので、リストでtagsが取得される。それを条件に展開するので、Pet.tag == tagをリストの要素分作って、sqlalchemy._or()の要素に展開するように実装している。 またlimitによる件数の制限は、取得したものについて行うようにしている(本当は検索するときに指定すべき)。

or_filters = or_(*[Pet.tag == tag for tag in tags])
pets = Pet.query.filter(or_filters).all()

SwaggerでWeb APIを作る - APIを実装する

この記事はFujitsu Advent Calendarの20日目です。

Swaggerとは?

前のポストではSwaggerでWeb APIを設計し、ドキュメント化、モックサーバの起動について書いた。 改めてSwaggerについて。

  • REST API設計とそのツール群
    • 仕様書(YAML形式)を書くことでそれから機能を作れたり、ドキュメント化して公開するなど仕様の齟齬をなくす
    • メンテされなくなったExcelで書いた仕様書といったものを防げそう
  • REST APIの標準化団体Open API Initiativeが仕様として採用した

Flaskサーバを実装

使うAPI仕様書は、出来合いのPetstore(Simple)のものを使う(Swagger Editorで[File] > [Open Example...]でpetstore_simple.yamlを選択)。その仕様に沿ったサーバ機能の雛形は、同様にSwagger Editorの[Generate Server]から好みのフレームワークを選択することで取得できる。

ディレクトリ構造は以下のようなかたち。 swagger.yamlAPI仕様書。ロジックはdefault_controller.pyにつくる。

.
├── LICENSE
├── README.md
├── __init__.py
├── app.py
├── controllers
│   ├── __init__.py
│   └── default_controller.py
└── swagger
    └── swagger.yaml

Swaggerに準拠したFlaskのフレームワークとしては、connexionがある。 hjacobs/connexion-exampleに習い、今回はデータを辞書として持ち回るように実装する。

まずはPythonのライブラリを2つほどインストールする。

$ pip install Flask connexion

次にアプリケーションの起点であるapp.pyを見てみる。
API仕様書のswagger.yamlapp.add_apiで読み込み、8080ポートでサーバを起動することが書いてあるのみ。

#!/usr/bin/env python3

import connexion
        
if __name__ == '__main__':
    app = connexion.App(__name__, specification_dir='./swagger/')
    app.add_api('swagger.yaml')
    app.run(port=8080)

default_controller.pyが処理を書くメインとなっている。 このパスとメソッド名をドットでつないだものがoperationIdで指定しているものである。 connexionは、辞書で返却するとjsonで返してくれる(jsonifyなどでjson化しなくて良い)。

import logging
from connexion import NoContent

logger = logging.getLogger(__name__)

PETS = {}


def add_pet(pet):
    id = pet['id']
    # idがすでに使用されているか
    exists = id in PETS
    if exists:
        logger.info('If exists, update %s', id)
        PETS[id].update(pet)
    else:
        logger.info('If not exists, create %s', id)
        PETS[id] = pet
    return PETS[id], 200


def delete_pet(id):
    if id in PETS:
        logger.info('Deleting pet %s', id)
        del(PETS[id])
        return NoContent, 204
    else:
        return NoContent, 404
      
      
def find_pet_by_id(id):
    pet = PETS.get(id)
    return pet or (NoContent, 404)


def find_pets(tags=None, limit=None):
    return [pet for pet in PETS.values()
            if not tags or pet['tag'] in tags][:limit]

実装したREST APIを使ってみる

  • まずはデータのPost。
    • レスポンスとして投入したデータを返却する。
$ cat pet.json 
{"id":1,"name":"foo","tag":"dog"}
$ cat pet2.json 
{"id":2,"name":"bar", "tag":"cat"}
$ curl -i -X POST --header "Content-Type: application/json" --header "Accept: application/json" http://localhost:8080/api/pets -d @pet.json
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 46
Server: Werkzeug/0.11.11 Python/3.5.2
Date: Mon, 19 Dec 2016 14:02:06 GMT

{
  "tag": "dog",
  "name": "foo",
  "id": 1
}
$ curl -i -X POST --header "Content-Type: application/json" --header "Accept: application/json" ttp://localhost:8080/api/pets -d @pet2.json
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 46
Server: Werkzeug/0.11.11 Python/3.5.2
Date: Mon, 19 Dec 2016 14:02:11 GMT

{
  "tag": "cat",
  "name": "bar",
  "id": 2
}
  • 一覧を取得
$ curl -i http://localhost:8080/api/pets
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 118
Server: Werkzeug/0.11.11 Python/3.5.2
Date: Mon, 19 Dec 2016 14:32:26 GMT

[
  {
    "name": "foo",
    "id": 1,
    "tag": "dog"
  },
  {
    "name": "bar",
    "id": 2,
    "tag": "cat"
  }
]
  • 一覧を取得(取得件数を1件(limit=1)を指定)
$ curl -i http://localhost:8080/api/pets?limit=1
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 60
Server: Werkzeug/0.11.11 Python/3.5.2
Date: Mon, 19 Dec 2016 14:33:26 GMT

[
  {
    "name": "foo",
    "id": 1,
    "tag": "dog"
  }
]
  • 一覧を取得(tagsを指定)
    • dogのみ取得
$ curl -i http://localhost:8080/api/pets?tags="dog"
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 60
Server: Werkzeug/0.11.11 Python/3.5.2
Date: Mon, 19 Dec 2016 14:35:20 GMT

[
  {
    "name": "foo",
    "id": 1,
    "tag": "dog"
  }
]
  • catまたはdogのtagのものを取得
$ curl -i http://localhost:8080/api/pets?tags="dog,cat"
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 118
Server: Werkzeug/0.11.11 Python/3.5.2
Date: Mon, 19 Dec 2016 14:35:36 GMT

[
  {
    "name": "foo",
    "id": 1,
    "tag": "dog"
  },
  {
    "name": "bar",
    "id": 2,
    "tag": "cat"
  }
]
  • 削除してから再び取得
    • 削除して204となる
    • id=1を取得して404となる
$ curl -i -X DELETE http://localhost:8080/api/pets/1
HTTP/1.0 204 NO CONTENT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Server: Werkzeug/0.11.11 Python/3.5.2
Date: Mon, 19 Dec 2016 14:37:23 GMT
$ curl -i http://localhost:8080/api/pets/1
HTTP/1.0 404 NOT FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 0
Server: Werkzeug/0.11.11 Python/3.5.2
Date: Mon, 19 Dec 2016 14:37:43 GMT

その他

レスポンスはpretty printされて返却される

github.com

add_apiのオプションとして渡せたらいいなという議論がある。今はflask_compressを使う。

x-swagger-router-controller:をオペレーションの上に書くとエラー

x-swagger-router-controllerとしては、controllerのモジュールのパス(上記の例だとcontrollers.default_controllerを記載して、oeprationIdはメソッドのみを記載するといった使い方ができそうだけど、URL直下に書くと以下のようなエラーが起きる。

Traceback (most recent call last):
  File "app.py", line 8, in <module>
    app.add_api('swagger.yaml')
  File "/Users/hermesian/.anyenv/envs/pyenv/versions/dev352/lib/python3.5/site-packages/connexion/app.py", line 146, in add_api
    debug=self.debug)
  File "/Users/hermesian/.anyenv/envs/pyenv/versions/dev352/lib/python3.5/site-packages/connexion/api.py", line 102, in __init__
    validate_spec(spec)
  File "/Users/hermesian/.anyenv/envs/pyenv/versions/dev352/lib/python3.5/site-packages/swagger_spec_validator/validator20.py", line 88, in validate_spec
    validate_apis(apis, bound_deref)
  File "/Users/hermesian/.anyenv/envs/pyenv/versions/dev352/lib/python3.5/site-packages/swagger_spec_validator/validator20.py", line 151, in validate_apis
    oper_params = deref(oper_body.get('parameters', []))
AttributeError: 'str' object has no attribute 'get'

ドキュメントではオペレーション直下に書いているけど、それならoperationIdだけで良さそう。

API仕様書のresponseを実装側で上書きしてしまう

API仕様書ではリターンコードが204だが、アプリケーション側で200を返すように書くと、後者で返される。レスポンスデータについても同様。 これ結構悩ましい問題。レスポンスのバリデーションが必要そう。

SwaggerでWeb APIを作る - Web APIの設計

Web APIの設計

Web APIの設計でExcelを使って定義書を作成していたが、仕様の変更等によって気がついたら設計書とシステムに乖離しているなんてことがあった。そのようなことがないようにWeb APIの定義情報を常に正とするようなアプリにしたい。

そこでSwaggerは、いくつかの企業によるコンソーシアムによって、Web APIの標準化を行うための規約とそのツール群を使う。

Swagger

Swaggerのサイトに行くとSwagger-editorやSwagger-UIなどツールがいくつかあるけど、swagger-nodeとbootprint-openapiで一通りできる。

GitHub - swagger-api/swagger-node: Swagger module for node.js

$ npm install -g swagger

GitHub - bootprint/bootprint-openapi: Bootprint-module to render OpenAPI specifications

$ npm install -g bootprint
$ npm install -g bootprint-openapi
$ npm -g install html-inline

手順としては、以下。

Swagger-editorでWeb API定義ファイルを作成する

はじめにプロジェクトのディレクトリを作る。 その際にNodeJSのwebフレームワークを選択させられる。これは後で作るモックサーバをカスタマイズするときに改造していくものである(Flaskが対応していたら便利だけど)。 expressを選択する。

$ swagger project create example

次に生成されたディレクトリに降りて、エディタを起動する。

$ cd example/
$ swagger project edit

Webブラウザが起動し、エディタが表示される。 GUIに対応できない環境(x windowを起動していないサーバなど)で利用するときは、-s | --silentを付ける。

今回はSwagger Editorのpetstores_simple.yamlを使う。

ドキュメント化する

bootprint-openapiでSwaggerファイルをHTML化する。

$ pwd
example
$ bootprint openapi api/swagger/swagger.yaml target

するとtargetディレクトリ配下にhtml, css, javascriptが書き出され、適当なwebサーバに配置することで閲覧できる。 さらにcss, javascriptをHTMLにまとめたいときは下記のようにし、dist.htmlを公開すれば良い。

$ html-inline target/index.html > target/dist.html

モックサーバを立てる

モックサーバはサーバ機能をまだ実装していない段階で、クライアント側の仕様検討、実装を待ちにすることなく行う目的で建てる。

$ pwd
example
$ swagger project start -m

クライアント側(同一端末だが)でAPIを叩く。 (2016.12.12 修正: 下記で/petsを叩いていたのだけど、そもそもbathPath: /apiなので、 そのようなAPIはないのは当たり前だった。

$ curl -i http://localhost:10010/api/pets
HTTP/1.1 404 Not Found
X-Powered-By: Express
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 21
Date: Mon, 12 Dec 2016 04:50:26 GMT
Connection: keep-alive

Cannot GET /api/pets

おや? 同じ事象がRunning in mock modeチュートリアル例でも起きる。

調べてみると、下記のissueに遭遇。x-swagger-router-controllerを指定しないとダメとのこと。 これを付けるのは、pathかoperationの階層で定義すれば良い。これとoperationIdAPIから呼ばれるメソッドを指定する。

issueでも言われているけど、モックなのにこれを指定しないといけないのは使い勝手が悪い気が...。 Flaskでswaggerに沿ったサーバ機能を作ることができるconnexionでは、むしろoperationレベルでのみでxx-swagger-router-controllerを設定できる。これだとoperationIdのみでもう良くないかと思えてきた。なのでモック使うとき以外は特に使用しないことにする。

github.com

pathに含まれるoperationで全て同じとする。

..(省略)..

  /pets:
    x-swagger-router-controller: pets

..(省略)..

  /pets/{id}:
    x-swagger-router-controller: pets

..(省略)..

改めてクライアント側で叩く。 そのまま使うと決められた値(numberなら1, stringなら"Sample text")が返ってくる。 レスポンスをカスタマイズしたい場合は、Implementing mock mode controllersを参照。

$ curl -X GET -i http://localhost:10010/api/pets
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:08:50 GMT
Connection: keep-alive
Content-Length: 51

[{"id":1,"name":"Sample text","tag":"Sample text"}]
$ cat pet.json 
{"id":1,"name":"foo","tag":"dog"}
$ curl -X POST --header "Content-Type: application/json" --header "Accept: application/json" -i http://localhost:10010/api/pets -d @pet.json
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:29:25 GMT
Connection: keep-alive
Content-Length: 49

{"id":1,"name":"Sample text","tag":"Sample text"}
$ curl -X GET -i http://localhost:10010/api/pets/1
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:36:02 GMT
Connection: keep-alive
Content-Length: 49

{"id":1,"name":"Sample text","tag":"Sample text"}
$ curl -X DELETE -i http://localhost:10010/api/pets/1
HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:37:25 GMT
Connection: keep-alive
Content-Length: 196

{"message":"Response validation failed: void does not allow a value","code":"INVALID_TYPE","failedValidation":true,"path":["paths","/pets/{id}","delete","responses","204"],"originalResponse":"{}"}

最後だけこけた。どうやらレスポンスが空は許可されていないみたい。 対象療法だけど、レスポンスを追加した。

    delete:
      description: deletes a single pet based on the ID supplied
      operationId: deletePet
      parameters:
        - name: id
          in: path
          description: ID of pet to delete
          required: true
          type: integer
          format: int64
      responses:
        '200':
          description: pet deleted
          schema:
            $ref: '#/definitions/pet'
        default:
          description: unexpected error
          schema:
            $ref: '#/definitions/errorModel'

どうにか。

$ curl -X DELETE -i http://localhost:10010/api/pets/1
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:51:11 GMT
Connection: keep-alive
Content-Length: 49

{"id":1,"name":"Sample text","tag":"Sample text"}

サーバ機能の雛形を生成する

最後にAPI仕様書からサーバ機能の雛形を作る

swagger-nodeの方ではないSwagger-Editorを用いると、サーバ機能のダウンロードができる(swagger-codegenでも良い)。 インストールしても良いが、Dockerイメージがあるので、コンテナにて行う。

$ docker pull swaggerapi/swagger-editor
$ docker run -p 80:8080 swaggerapi/swagger-editor

あとはhttp://(YOUR_CONTAINER_URI)にアクセスし、API仕様書の内容をコピー&ペーストして、[Generate Server]からお好みのフレームワークの雛形をダウンロードする。これからFlaskでサーバ機能を作るので、Flaskを選択する。

NvidiaのDeep Learning Quest(無料分)を受ける

すでにひとしきり広まった感があるDeep Learning。 Nvidiaがかなり本腰入れていて、それ向けの教育プログラムまである。

f:id:hermesian:20160504130503p:plain

どういうものかは知っておきたいので、無料枠で受けれる「ディープラーニング入門」を受講してみよう。実際AWS上に構築された環境を触りながらできるハンズオン形式で、有料のプログラムもAWSの利用料金ぐらいの値段みたい(とNvidia Deep Learning Day 2016 Springで紹介されていた)。

私たちは、あなたがあなたのアプリケーションは、研究者や開発者として必要な最高のどのフレームワークスーツを決める手助けを目的とした深い学習> のための最も一般的なソフトウェアフレームワークを見学します。この見学では、あなたのアプリケーションに一番適したディープラーニングフレーム> ワークを決定する事をゴールにします。ディープラーニングの予備知識は必要ありません。

たしかにDLフレームワークは乱立している印象がある。Software links « Deep Learningを見ると38種もあるのか。

起動に4分程度かかるとのこと。Jupyter Notebook形式なのね。 始まるとなんとタイマーが起動して、55分の間だけ利用できるとのこと。

f:id:hermesian:20160504130510p:plain

各DLフレームワークの採用基準も記載があった。 NVDLDでも下記のような比較がされてたけど、TheanoとTorch7の比較(性能面、機能面)があまり理解できていない。 これはもう使ってみないとわからない世界なのかも。 Lua言語の習得コスト考えるとTheanoとかPythonサポートしているものになりそうな印象。

GPUリソースをどうやって調達しようかな...。

Djangoアプリケーションのデプロイ

開発サーバでなく、製品版ではどうするかというと2通りの方法があるみたい。

  1. Apache Httpサーバ + mod_wsgi
  2. Nginx + Gunicorn

2.の方がパフォーマンスが優れているという話も見かけたけど、今回は1.を試してみる。 wsgiは"ウィスギィ"と読むのか。

検証環境として、Virtualbox上に立てたCentOS 7を使う。

 cat /etc/centos-release
CentOS Linux release 7.2.1511 (Core)

webブラウザから確認するため、VagrantfileにIPアドレスを固定するよう設定する。

  • Vagrantfile
...
  config.vm.network "private_network", ip: "192.168.33.10"
...
Python 3.5.1のインストール

デフォルトでインストールされているPythonのバージョンが2.7.5なので、3.5.1をインストールする。

# mkdir tmp
# cd tmp
# wget https://www.python.org/ftp/python/3.5.1/Python-3.5.1.tgz
# tar -xzf Python-3.5.1.tgz
# cd Python-3.5.1
# ./configure --enable-shared
# make
# make altinstall

インストールの確認をしたが、関連するライブラリのパスが通っていない...。 パスを通す。

# /usr/bin/python3.5 -V/usr/bin/python3.5: error while loading shared libraries: libpython3.5m.so.1.0: cannot open shared object file: No such file or directory
# echo "/usr/local/lib/python3.5" > /etc/ld.so.conf.d/python33.conf
# echo "/usr/local/lib" >> /etc/ld.so.conf.d/python33.conf
# ldconfig
Apache Httpサーバ およびmod_wsgiのインストール

tmpディレクトリのままでApache Httpサーバとmod_wsgiをインストールする。

# yum install httpd
# wget https://github.com/GrahamDumpleton/mod_wsgi/archive/4.5.2.tar.gz
# tar -xzf 4.5.2.tar.gz
# cd mod_wsgi-4.5.2/
# ./configure --with-python=/usr/local/bin/python3.5
# make
# make install
Djangoのインストール

pipのバージョンが古いかったので、アップグレードも実施。

# pip3.5 freeze
You are using pip version 7.1.2, however version 8.1.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
# /usr/local/bin/pip3.5 install --upgrade pip
# /usr/local/bin/pip3.5 install django
確認用プロジェクトで確認する

helloプロジェクトを作成。

# su - vagrant
$ cd ~
$ mkdir -p dev/python/django
$ cd dev/python/django/
$ django-admin startproject hello

マイグレーション

$ /usr/local/bin/python3.5 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, sessions, contenttypes
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying sessions.0001_initial... OK

管理者アカウントを作っておいて、管理画面の表示も確認しよう。

$ /usr/local/bin/python3.5 manage.py createsuperuser
Username (leave blank to use 'vagrant'): admin
Email address: admin@email.com
Password:
Password (again):
Superuser created successfully.

前回はstaticフォルダにJavascriptやらCSSを入れて参照していたけど、管理画面のものはどこにあるのかなと思ったらそれをstaticフォルダに集めれとのこと。これをしないとあとで管理画面のスタイルが崩れて表示される。

collectstaticコマンド を実行するとSTATIC_ROOTで指定したディレクトリに格納される。

  • hello/settings.py
...
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
...

それでもって

$ /usr/local/bin/python3.5 manage.py collectstatic
mod_wsgiの設定
oadModule wsgi_module modules/mod_wsgi.so

WSGIDaemonProcess hello python-path=/home/vagrant/dev/python/django/hello
WSGIProcessGroup hello
WSGIScriptAlias / /home/vagrant/dev/python/django/hello/hello/wsgi.py process-group=hello

<Directory /home/vagrant/dev/python/django/hello/hello>
    <Files wsgi.py>
        Require all granted
    </Files>
</Directory>

Alias /static /home/vagrant/dev/python/django/hello/static
<Directory /home/vagrant/dev/python/django/hello/static>
    Require all granted
</Directory>

apacheユーザがhelloプロジェクトにアクセスできるようにする。

$ sudo usermod -a -G vagrant apache
$ sudo chmod 775 -R ~/dev/python/django/hello/
Apache Httpサーバの起動

あとはApache Httpサーバを起動して、http://192.168.33.10/http://192.168.33.10/adminで開発用サーバと同じ画面が見れるはず。

# systemctl start httpd
# systemctl enable httpd

ただ1点気になったのが、Apache Httpサーバのエラーログに/はないと言われるのはなんでだろう。。

[wsgi:error] [pid 4116] Not Found: /