LoginSignup
4
5

More than 3 years have passed since last update.

pipenvによるpythonアプリケーション実行環境をDockerとVirtualBox(Vagrant)で作る

Last updated at Posted at 2019-07-10

前置き

pythonでバッチやWebアプリケーション(Django/Flask等)の環境を作るときのベースイメージとなるものをDockerとVagrantで作ったのでその記録です。(DockerもVagrant(VirtualBox)もベースはCentOS(7.6)です)

本当は、nginxやuWSGIなどとの連携や簡単なアプリケーションまで作って載せよう思いましたが、長くなったので、気が向いたら書くことにします。

Dockerコンテナ版

前提

sshで接続できるpipenvで作るpython実行環境のDockerコンテナのサンプルです。
これをこのまま利用するというよりはこれでビルドしたイメージをベースにDjangoやuWSGIをインストールした派生イメージを作ったり、pythonのバッチアプリケーションを実行するコンテナを作ったりする想定で作ったものです。

試したDockerのバージョンは以下の通りです。

Client: Docker Engine - Community
 Version:           18.09.2
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        6247962
 Built:             Sun Feb 10 04:12:39 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.2
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.6
  Git commit:       6247962
  Built:            Sun Feb 10 04:13:06 2019
  OS/Arch:          linux/amd64
  Experimental:     false

Dockerfileの中身

FROM centos:7

ARG APP_USR_PWD
ARG BASE_GROUP=apps
ARG APP_USER=app
ARG APP_HOME=/home/app
# only python3.x.y
ARG PYTHON_VERSION=3.7.4

RUN test -n "${APP_USR_PWD}" && \
    groupadd ${BASE_GROUP} && \
    useradd -d ${APP_HOME} -G ${BASE_GROUP} -s /bin/bash ${APP_USER} && \
    yum install -y epel-release && \
    yum install -y https://centos7.iuscommunity.org/ius-release.rpm && \
    yum install -y sudo python-pip openssh openssh-server passwd logrotate sudo wget vim jq \
    gcc git libffi-devel zlib-devel bzip2 bzip2-devel readline readline-devel sqlite sqlite-devel openssl openssl-devel && \
    yum -y update && yum -y clean all && rm -rf /var/cache/yum/ && \
    echo "${APP_USER}:${APP_USR_PWD}" | chpasswd && \
    echo "${APP_USER} ALL=(ALL) ALL" > /etc/sudoers.d/${APP_USER} && \
    pip install pip --upgrade && pip install pipenv

# install pyenv
RUN git clone https://github.com/pyenv/pyenv.git ${APP_HOME}/.pyenv && \
    chown -R ${APP_USER}:${BASE_GROUP} ${APP_HOME}/ && \
    echo '' >> ${APP_HOME}/.bash_profile && \
    echo 'export PYENV_ROOT="${HOME}/.pyenv"' >> ${APP_HOME}/.bash_profile && \
    echo 'export PATH="${PYENV_ROOT}/bin:${PATH}"' >> ${APP_HOME}/.bash_profile && \
    echo 'if command -v pyenv 1>/dev/null 2>&1; then' >> ${APP_HOME}/.bash_profile && \
    echo '  eval "$(pyenv init -)"' >> ${APP_HOME}/.bash_profile && \
    echo 'fi' >> ${APP_HOME}/.bash_profile && \
    echo "export PIPENV_DEFAULT_PYTHON_VERSION=${PYTHON_VERSION}" >> ${APP_HOME}/.bash_profile && \
    echo 'export PIPENV_VENV_IN_PROJECT=true' >> ${APP_HOME}/.bash_profile

# install python.
RUN su - ${APP_USER} -c "pyenv install ${PYTHON_VERSION} && pyenv global ${PYTHON_VERSION} && pyenv rehash && pip install --upgrade pip && pip install pipenv"

# setup ssh.
RUN ssh-keygen -q -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa && \
    ssh-keygen -q -f /etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa && \
    ssh-keygen -q -f /etc/ssh/ssh_host_ed25519_key -N '' -t ed25519 && \
    mkdir -p ${APP_HOME}/.ssh && chmod 700 ${APP_HOME}/.ssh
# Add public key to container.
ADD id_rsa.pub ${APP_HOME}/.ssh/authorized_keys
RUN chown -R ${APP_USER}:${BASE_GROUP} ${APP_HOME}/.ssh/

EXPOSE 22

ENTRYPOINT ["/usr/sbin/sshd", "-D"]

事前作業

  • Dockerfileと同じディレクトリにSSHログインする秘密鍵に対応する公開鍵をid_rsa.pubというファイル名で配置すること!!
  • 社内のプロキシ環境下の場合は、DockerfileのFROMの直後当たりに環境変数の設定を追加する。

例)

FROM centos:7

ENV http_proxy=http://proxy.xx.yy:9999 \
    https_proxy=http://proxy.xx.yy:9999

# 以下省略・・・

ビルド方法

docker build -t {イメージ名}[:{タグ}] --build-arg APP_USR_PWD={SSHログインユーザがsudoするときに必要なパスワード}

上記以外に以下の値が--build-argで上書き可能である。

  • BASE_GROUP:アプリケーションの実行(=SSH接続)ユーザの所属グループ(デフォルトapps
  • APP_USER:アプリケーションの実行(=SSH接続)ユーザ名(デフォルトapp
  • APP_HOME:アプリケーションの実行(=SSH接続)ユーザのホームディレクトリ(デフォルト/home/app
  • PYTHON_VERSION:pipenvでインストールするアプリケーションの実行(=SSH接続)ユーザのグローバルのpythonバージョン(デフォルト3.7.4)

PYTHON_VERSIONはすべてのバージョンを確認しているわけではないので、指定するものによってはインストールするパッケージが不足している可能性もあります。

起動方法

docker run -d -p ホスト側のポート番号:22 --name {任意のコンテナ名} {イメージ名}[:{タグ}]

dockerホストからのアクセス

  • root
    docker exec -it {任意のコンテナ名} bash
  • app
    docker exec -it --user app {任意のコンテナ名}

Dockerホスト外からSSH接続方法

DockerホストOS側のファイアーウォール設定などで起動方法に記載した「ホスト側のポート番号」でアクセス可能であること。
(この点についてはDockerホスト環境に依存するので割愛)

接続情報は以下の通りです。適宜、利用するSSHクライアントツールに以下の情報を指定してください。

  • ホスト:接続元から見たDockerホストのホスト名 or IPアドレス
  • ポート番号:起動方法に記載した「ホスト側のポート番号」
  • ユーザ:app(--build-arg APP_USER=xxxとした場合はxxx)
  • 秘密鍵:Dockerfileと同じディレクトリに配置した公開鍵の秘密鍵

Vagrant+VirtualBox版

前提

VagrantのproviderはVirtualBoxを前提としていますが、VagrantfileのVirtualBox依存部分を他のプロバイダーに書き換えれば、問題なく動くと思います。
Vagrant版を作ったのはDockerが動かない(正確には動かすのにいろいろな作業が必要になりそう)環境の人がいたので、急遽つくったのものなので、
DockerfileのRUNでいろいろインストールしているところを外だしのprovistion.shに移したものです。
個人的には、実稼働環境の構築も考慮して、Ansible-Playbookで書きたいところなので、そのうち気が向いたら書き直します。

試したVagrantとVirutualBOXのバージョンは以下の通りです。

$ VBoxManage --version
6.0.8r130520
$ vagrant version
Installed Version: 2.2.5
Latest Version: 2.2.5

You're running an up-to-date version of Vagrant!

事前作業

  • Vagarnt sshでログインできるのでSSHの設定は特別には行っていません。
  • 社内のプロキシ環境下の場合は、以下の作業を実施しておくこと。
    • vagrant-proxyconfをインストールする。
      vagrant plugin install vagrant-proxyconf
    • 環境変数にhttp_proxy/https_proxy/no_proxyを登録する。)

Vagrantfileとprovision.sh

Vagrantfileのprovision設定をinlineで書くとみづらい&書きづらいのでprovision.shに外だししました。

forwarded_portのホスト側のポート番号、private_networkのIPアドレス、sync_folderのパス部分は各自の環境に合わせて変更してください。

Vagranfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  # 利用するプラグイン定義
  config.vagrant.plugins = ["vagrant-vbguest", "vagrant-winnfsd"]

  # boxイメージ指定
  config.vm.box = "centos/7"
  config.vm.box_version = "1902.01"
  config.vm.box_check_update = false

  # GuestAdditionsを更新しない
  config.vbguest.auto_update = false

  # プロキシ設定
  if Vagrant.has_plugin?("vagrant-proxyconf")
    if ENV['http_proxy']
      config.proxy.http = ENV['http_proxy']
    end
    if ENV['https_proxy']
      config.proxy.https = ENV['https_proxy']
    end
    if ENV['no_proxy']
      config.proxy.no_proxy = ENV['no_proxy']
    end
  end

  # ポートフォワード設定(外部PCからのSSHアクセス用)
  config.vm.network "forwarded_port", id: "ssh", guest: 22, host: 2222

  # NFSに必要なprivate(host only)ネットワーク
  # ipの代わりに`type: "dhcp"`でも可。
  config.vm.network :private_network, id: "default-network", ip: "192.168.132.101"

  # ホスト名
  config.vm.hostname = "app.python.localohost"

  # ローカルでコーディングしたものをNFSマウントでゲスト側に認識させるためのディレクトリ
  # ⇒アプリを置く場所の想定です。
  config.vm.synced_folder ".", "/home/vagrant/apps", type: "nfs", nfs_export: true, nfs_version: 3

  # ゲストOSのリソースは適宜変更してください。
  config.vm.provider :virtualbox do |vb|
    vb.name = "python-app" # VirtualBox上のゲストOS名
    vb.gui = false
    vb.memory = 2048
    vb.cpus = 2
    vb.check_guest_additions = false
    vb.functional_vboxsf     = false
  end
  # python3に必要なパッケージやpipenv(pyenv)のインストールなど。
  config.vm.provision :shell do |shell|
    shell.name = 'provision'
    shell.env = {
      :PYTHON_VERSION => "3.7.4",
    }
    shell.path = "provision.sh"
  end
end
provision.sh
#!/bin/bash
set -eo pipefail

echo "Start provision!!"

# パラメータ定義
# pythonのバージョン定義(環境変数で上書き可能)
PYTHON_VERSION=${PYTHON_VERSION:-"3.7.4"}
# めんどくさいのでvagrantをそのまま使う。
APP_GROUP="vagrant"
APP_USER="vagrant"
APP_HOME="/home/${APP_USER}"

# rootのssh接続を不可/パスなし接続不可(公開鍵認証のみ)
sed -i -e 's/#PermitRootLogin yes/PermitRootLogin no/' \
       -e 's/#PermitEmptyPasswords no/PermitEmptyPasswords no/' /etc/ssh/sshd_config

# パッケージのインストール等
# epelとiusの追加
yum install -y epel-release https://centos7.iuscommunity.org/ius-release.rpm
# update
yum update -y
# 必要なパッケージの追加(pythonに特化すれば全部ではなく、一部はサーバー上に必要そうなコマンド群も含む)
yum install -y wget jq git openssl \
               gcc python-pip python-devel \
               zlib-devel libffi-devel bzip2-devel \
               openssl-devel ncurses-devel sqlite-devel \
               readline-devel tk-devel gdbm-devel \
               libuuid-devel xz-devel systemd-devel
# お掃除
yum -y clean all && rm -rf /var/cache/yum/

# pyenvをインストールする
git clone https://github.com/pyenv/pyenv.git ${APP_HOME}/.pyenv
# アプリユーザで利用できるようにインストール
cat << EOS >> ${APP_HOME}/.bash_profile
# Add pyenv setting
export PYENV_ROOT="\$HOME/.pyenv"
export PATH="\$PYENV_ROOT/bin:\$PATH"
if command -v pyenv 1>/dev/null 2>&1; then
    eval "\$(pyenv init -)"
fi
export PIPENV_DEFAULT_PYTHON_VERSION=${PYTHON_VERSION}
export PIPENV_VENV_IN_PROJECT=true
EOS
chown -R ${APP_USER}:${APP_GROUP} ${APP_HOME}/.pyenv
# アプリケーションユーザ用のpythonはpyenvでglobalにセット
su - ${APP_USER} -c "pyenv install ${PYTHON_VERSION} && pyenv global ${PYTHON_VERSION} && pyenv rehash && pip install --upgrade pip && pip install pipenv"

echo "Fisnish provision!!"

起動方法

Vagrantfile/provision.shを同じディレクトリにおいて、そのディレクトリをカレントにした状態で

vagrant up

ホストからのアクセス

vagrant ssh

VirtualBoxホスト外からSSH接続方法

ホストOS側のファイアーウォール設定などで起動方法に記載した「ホスト側のポート番号」でアクセス可能であること。

接続情報は以下の通りです。適宜、利用するSSHクライアントツールに以下の情報を指定してください。

  • ホスト:接続元から見たVirtualBoxホストのホスト名 or IPアドレス
  • ポート番号:2222
  • ユーザ:vagrant
  • 秘密鍵:vagrant ssh-configで実行した``に秘密鍵のパスが記載されています。

例)

Host default
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile {カレントディレクトリ}/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

なお、これはUnix/Linux/Macなどの${HOME}/.ssh/configに記載する内容となります。ただ、上の設定はlocalhostを前提にしているのでHostNameに指定する値は接続元から見たVirtualBOXホストのホスト名 or IPアドレスにしてください。
また、先頭のdefaultはVagrantの識別名なのでここも任意変更可能で、例えばHost pythonのように指定した場合、ssh pythonのように打つことでログインすることができます。(Vagrantの仕様とは直接関係ないので細かいことは割愛)

SSH接続後の利用例(共通)

pipenvのことが書きたいわけではないので、ほんの触りだけ。。。

# プロジェクトディレクトリ作る。
mkdir test
cd test
# pipenv(pyenv)による仮想環境を作る
pipenv --python 3.7.4
# =>これでPipfileが作られる
# PyPIパッケージモジュールをインストールする(上位人気のPyPIパッケージ)
pipenv install simplejson setuptools requests python-dateutil PyYAML
# => Pipfileに追記され、Piplockファイルにインストールされたバージョン情報とか諸々書き込まれる
# あとは作るものに依存するので割愛します。

追記

↑のVagrantfileのconfig.vm.synced_folder ".", "/home/vagrant/apps", type: "nfs"の部分についてですが、VPN接続時にマウントエラーが発生するという事象にぶち当たりました。
回避方法は別記事にしましたので、ご参照ください。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5