前置き
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
を登録する。)
-
vagrant-proxyconfをインストールする。
Vagrantfileとprovision.sh
Vagrantfileのprovision設定をinlineで書くとみづらい&書きづらいのでprovision.shに外だししました。
forwarded_port
のホスト側のポート番号、private_network
のIPアドレス、sync_folder
のパス部分は各自の環境に合わせて変更してください。
# -*- 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
# !/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接続時にマウントエラーが発生するという事象にぶち当たりました。
回避方法は別記事にしましたので、ご参照ください。