はじめに
こんにちは, インターン生の田部と申します. IoT化がますます進展していく中, ラズパイを利用したプロダクトを開発したいという方もいらっしゃると思います. そこで, Ansistranoを使ってgitリポジトリからラズパイにデプロイをする方法をご紹介したいと思います. Ansistranoは, Capistranoライクに使えるツールで, Railsエンジニアでもキャッチアップしやすいので, ぜひ利用してみてください.
今回は, 主にAnsistranoで以下を行っていきます.
- ラズパイの初期設定
- FastAPIを使ったAPIサーバを起動時に実行するための設定
前提
raspbian OS(buster 2019-09-26)がインストールされている
ssh, wifiの設定
microSDにOSイメージを書き込んだ後にルート(/boot)ディレクトリに, ファイル名sshの空ファイル, wpa_supplicant.conf (参考) を作成する
ラズパイへ公開鍵を配布
以下のコマンドで公開鍵を配布します
ssh-copy-id -i [path_to_pubkey] pi@[raspi_ip_ddress]
IPアドレスの代わりに, [host_name].localでsshすることも可能です
ssh-copy-id -i [path_to_pubkey] pi@raspberrypi.local
※ 複数のラズパイが同一ネットワークにいる場合は, ラズパイのホスト名をユニークに設定しましょう
デプロイするリポジトリのディレクトリ構造
$ tree . ├── deploy │ ├── hosts │ ├── roles │ │ ├── reboot.yml │ │ ├── setup.sh │ │ └── setup_sub.yml │ ├── rollback.yml │ ├── deploy.yml │ └── templates │ └── sample.conf ├── main.py ├── README.md └── requirements.txt
FastAPIを利用したサンプルAPI
APIサーバを簡単に構築できるFastAPIを利用して簡単なAPIを作成します
FastAPIはドキュメントも自動生成してくれます
※ 必要条件: python3.6以降
# main.py from fastapi import FastAPI, Path from pydantic import BaseModel, Field app = FastAPI() class User(BaseModel): name: str email: str age: int sex: str = Field(..., regex="^male$|^female$") users = { 1: {"name": "taro", "email": "example1@test.com", "age": 20, "sex": "male"}, 2: {"name": "hana", "email": "example2@test.com", "age": 30, "sex": "female"}, 3: {"name": "hori", "email": "example3@test.com", "age": 40, "sex": "male"}, } def not_resource_error(user_id: int): return {'error': f'resource user{user_id} does not exist'} @app.get("/") async def hello(): return {"result": "Hello!"} @app.get("/users/") async def read_all_user(): return users @app.get("/users/{user_id}") async def read_user(*, user_id: int = Path(..., title="The ID of the user to get", ge=1)): if not user_id in users.keys(): return not_resource_error(user_id) user = users[user_id] return user
必要なモジュールのリスト
# requirements.txt fastapi == 0.48.0 pydantic uvicorn
Ansistranoのインストール
動作環境のAnsibleのバージョン
ansible --version ansible 2.5.1
以下のコマンドを実行 (参考)
ansible-galaxy install ansistrano.deploy ansistrano.rollback
Ansistranoの設定
playbookの作成
以下のようにplaybookを作成します
Capistranoでいう全体の設定ファイル(config/deploy.rb)に対応しています
#deploy.yml - hosts: all vars: ansistrano_keep_releases: 5 ansistrano_deploy_to: "/home/pi" ansistrano_deploy_via: git ansistrano_git_branch: develop ansistrano_git_repo: [user]@[host]:[project].git # gitリポジトリにはsshでアクセス ansistrano_git_identity_key_path: [path_to_git_secret_key] # gitにアクセスするための秘密鍵へのpathを設定 ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}/roles/setup_sub.yml" ansistrano_after_cleanup_tasks_file: "{{ playbook_dir}}/roles/reboot.yml" roles: - ansistrano.deploy
- デプロイ先の環境で固有の設定ファイルやディレクトリなどは, 上記に以下を追加することで, sharedディレクトリにコピーされ, 対象のファイルへのリンクが作成されます
ansistrano_shared_files: []
ansistrano_shared_paths: []
この設定を追加すれば, 各バージョン間で設定ファイルなどを保持することができます
# rollback.yml - hosts: all vars: ansistrano_deploy_to: "/home/pi" ansistrano_rollback_after_cleanup_tasks_file: "{{ playbook_dir }}/roles/reboot.yml" roles: - ansistrano.rollback
templatesディレクトリに設定ファイルを作成
- supervisorの設定ファイル
# sample.conf [program:sample] directory=/home/pi/current command=/home/pi/current/venv/bin/uvicorn main:app --host 0.0.0.0 process_name=%(program_name)s_%(process_num)02d numprocs=1 autostart=true autorestart=true redirect_stderr=true user=pi stdout_logfile=/var/log/sample.log
hookファイルの作成
今回は, 基本的にシェルスクリプトに書き出した処理を機能毎に実行していきます
Capistranoでいうtaskファイル(lib/tasks/*)に対応した処理です
# setup_sub.yml - name: setup language shell: bash setup.sh lang args: chdir: "{{ ansistrano_release_path.stdout }}/deploy/roles" - name: setup pubkey shell: bash setup.sh pubkey args: chdir: "{{ ansistrano_release_path.stdout }}/deploy/roles" - name: setup supervisor shell: bash setup.sh supv args: chdir: "{{ ansistrano_release_path.stdout }}/deploy/roles" - name: setup raspberry shell: bash setup.sh rasp args: chdir: "{{ ansistrano_release_path.stdout }}/deploy/roles" - name: setup python shell: bash deploy/roles/setup.sh python args: chdir: "{{ ansistrano_release_path.stdout }}" - name: pip install from requirements.txt pip: requirements: "{{ ansistrano_release_path.stdout }}/requirements.txt" executable: "{{ ansistrano_release_path.stdout }}/venv/bin/pip" - name: set supervisord sample.conf become: yes copy: src: "{{ ansistrano_release_path.stdout }}/deploy/templates/sample.conf" dest: /etc/supervisor/conf.d/sample.conf remote_src: yes mode: '0644'
# reboot.yml # 設定を反映させるためにラズパイを再起動 - name: raspi reboot shell: sleep 2 && shutdown -r now async: 1 poll: 0 become: yes ignore_errors: true - name: wait reboot wait_for_connection: delegate_to: localhost
- 書き出したシェルスクリプト
# setup.sh # pythonのバージョンを指定 PYTHON_VERSION=3.7.4 # 日本語に対応 language_setup(){ sudo apt-get update sudo apt-get install -y ttf-kochi-gothic xfonts-intl-japanese xfonts-intl-japanese-big xfonts-kaname; sudo apt-get install -y fcitx-mozc } # パスワード認証を無効化 pubkey_setup(){ sudo sh -c "cat /etc/ssh/sshd_config | sed s/#PasswordAuthentication' '/PasswordAuthentication' 'no' '#/ | sed s/#PubkeyAuthentication/PubkeyAuthentication/ > ./sshd_config" sudo cp ./sshd_config /etc/ssh/sshd_config rm -f ./sshd_config } # pythonのプログラムをデーモン実行するために使用 install_supervisord(){ sudo apt-get install -y supervisor } # locale, timezoneの設定 rasp_setup(){ sudo raspi-config nonint do_change_timezone "Asia/Tokyo" sudo raspi-config nonint do_change_locale ja_JP.UTF-8 } python_setup(){ # 補足を参照 sudo apt-get install -y --no-install-recommends make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev git clone https://github.com/pyenv/pyenv.git ~/.pyenv if [ -z "`cat ~/.bashrc | grep pyenv`" ]; then echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc echo 'eval "$(pyenv init -)"' >> ~/.bashrc fi if [ -z "`/home/pi/.pyenv/bin/pyenv versions | grep $PYTHON_VERSION`" ]; then /home/pi/.pyenv/bin/pyenv install -f $PYTHON_VERSION /home/pi/.pyenv/bin/pyenv local $PYTHON_VERSION fi if [ ! -e some_directory ]; then /home/pi/.pyenv/versions/${PYTHON_VERSION}/bin/python -m venv venv fi if [ -z "`cat ~/.bashrc | grep venv`" ]; then echo source ~/current/venv/bin/activate >> ~/.bashrc fi } case $1 in lang ) language_setup ;; pubkey ) pubkey_setup ;; supv ) install_supervisord ;; rasp ) rasp_setup ;; python ) python_setup ;; esac
- 補足
pyenvにおいてbuild時に推奨される環境
デプロイ先の情報を記載したファイルを作成
Capistranoでいう, 環境ごとの設定ファイル(config/deploy/[stage_name].rb)に対応しています.
# hosts [web] raspberrypi.local [all:vars] ansible_ssh_user=pi
デプロイ
- deployディレクトリに移動し, 以下のコマンドを実行します
ansible-playbook -i hosts deploy.yml --private-key=[path_to_private_key]
Capistranoでは以下のコマンドに相当します
bundle exec cap [stage_name] deploy
- 予め, ssh-agentを利用して, 秘密鍵を登録しておくと秘密鍵を毎回指定せずに済みます
ssh-add [path_to_private_key]
ラズパイで立ち上がったAPIサーバーにアクセス
http://[raspi_ip_address]:8000 にアクセスして, 以下が返ってくるはずです
{"result":"Hello!"}
http://[raspi_ip_address]:8000/docs にアクセスすると, でAPIドキュメントを参照できます
宣伝
RenoSysでは, Railsエンジニアを募集中です.
ラズパイを活用したプロダクト開発などにも携われます.
en-gage.net