Windows + Win32-OpenSSH環境へ、FabricでDjango + IISアプリをデプロイしてみた

前回IISDjangoをホストしてみたので、今回はそれをデプロイする方法を試してみます。

 

事前調査

実現できそうなデプロイツールの調査

今後のことも考えて、Windowsに限定しないデプロイツールを調べてみたところ、

などがありました。

そのため、Windowsで使えそうなFabricを、今回は試してみることにしました。

 

Windows上でのSSHDについて

上記のFabricの記事ではfreeSSHdを使っていました。
freeSSHd and freeFTPd - open source SSH and SFTP servers for Windows

ただ、最近Microsoft公式のOpenSSHがプレリリースされたという記事を見かけました。

せっかくなので、今回使うOpenSSHはプレリリース版のWin32-OpenSSHとしました。

 
なお、現時点ではWin32-OpenSSHのバージョンは、

  • 10_13_2015
  • 11_09_2015

の2つがあります。
Releases · PowerShell/Win32-OpenSSH

後者のほうがバージョンが新しく、また、Widnowsサービスに登録もできるようになっています。

ただ、実際に使ってみたところ、同じ内容のfabfile・環境にも関わらず、11_09_2015版では文字化けやエラーが出て動作しませんでした。

10_13_2015
[<host_name>] run: PsExec.exe -u Administrator -p root %windir%\system32\inetsrv\appcmd list site
[<host_name>] out:
[<host_name>] out: PsExec v2.11 - Execute processes remotely
[<host_name>] out: Copyright (C) 2001-2014 Mark Russinovich
[<host_name>] out: Sysinternals - www.sysinternals.com
[<host_name>] out:
[<host_name>] out: C:\WINDOWS\system32\inetsrv\appcmd exited with error code 0.
[<host_name>] out:
11_09_2015

2行目の[20h[?7hが文字化けをしている部分です。

[<host_name>] run: PsExec.exe -u Administrator -p root %windir%\system32\inetsrv\appcmd list site
[<host_name>] out: [20h[?7h
[<host_name>] out: PsExec v2.11 - Execute processes remotely
[<host_name>] out: Copyright (C) 2001-2014 Mark Russinovich
[<host_name>] out: Sysinternals - www.sysinternals.com
[<host_name>] out:
[<host_name>] out: C:\WINDOWS\system32\inetsrv\appcmd exited with error code -1073741502.
[<host_name>] out:

 
今後のリリースで改善されるかもしれないので、今回は正常に動作する10_13_2015版を使うことにしました。

 

環境

ローカルマシン

 

リモートマシン
事前に構築しておくもの

 

後述の手順で構築するもの

 

Fabricで構築するもの
  • Djangoアプリは、Bitbucketのプライベートリポジトリからgit clone
    • 後述する理由により、SSH接続ではなくhttpsによるユーザー名とパスワードによる認証とした
    • clone先は、C:\django_appsの下
  • virtualenv環境の構築
  • git cloneしたrequirements.txtより、virtualenv環境へpip install
  • Djangomanage.py migrateの実行
  • Default Web SitePhysicalPathを設定
  • IISのサービス停止と起動

 

今回扱わないもの

 

リモートマシンの環境設定

以下を参考に、リモートマシンのOpen-SSHまわりの環境設定を行います。

 

OpenSSHまわりの設定

管理者のコマンドプロンプトで実行します。

# ダウンロード
C:\>bitsadmin.exe /TRANSFER OpenSSH https://github.com/PowerShell/Win32-OpenSSH/releases/download/10_13_2015/OpenSSH-Win32.zip c:\OpenSSH-Win32.zip
...
DISPLAY: 'OpenSSH' TYPE: DOWNLOAD STATE: TRANSFERRED
PRIORITY: NORMAL FILES: 1 / 1 BYTES: 10295557 / 10295557 (100%)
Transfer complete.

# zipファイルを解凍
# コマンドプロンプトからPowerShellを呼んでいるけど、標準で解凍する方法がこれしかないので仕方ない...
C:\>powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('C:\OpenSSH-Win32.zip', 'C:\'); }"

# key-gen
C:\>cd OpenSSH-Win32
C:\OpenSSH-Win32>ssh-keygen.exe -t rsa -f ssh_host_rsa_key
C:\OpenSSH-Win32>ssh-keygen.exe -t dsa -f ssh_host_dsa_key
C:\OpenSSH-Win32>ssh-keygen.exe -t ecdsa -f ssh_host_ecdsa_key
C:\OpenSSH-Win32>ssh-keygen.exe -t ed25519 -f ssh_host_ed25519_key

# C:\OpenSSH-Win32の中に鍵ができているのを確認
C:\OpenSSH-Win32>dir /b
setup-ssh-lsa.cmd
sftp-server.exe
sftp.exe
ssh-keygen.exe
ssh.exe
sshd.exe
sshd_config
ssh_host_dsa_key
ssh_host_dsa_key.pub
ssh_host_ecdsa_key
ssh_host_ecdsa_key.pub
ssh_host_ed25519_key
ssh_host_ed25519_key.pub
ssh_host_rsa_key
ssh_host_rsa_key.pub
x64
x86

# ファイアウォールのポートをオープン
C:\OpenSSH-Win32>netsh advfirewall firewall add rule protocol=tcp localport=22 dir=in action=allow name="SSH"
OK

参考

 

Bitbucketまわりの設定

Bitbucketからcloneなどができるよう、以下を参考に作業を行います。
Bitbucketアカウント作成〜ローカルのGit既存プロジェクトをインポート(push) | EasyRamble

作業は、以下のとおりです。

  • Git for Windowsのインストール
  • Bitbucket接続用の鍵生成とBitbucketへの鍵登録
C:\OpenSSH-Win32>ssh-keygen -t rsa -f %USERPROFILE%\.ssh\bitbucket_rsa

 

  • Bitbucket接続設定ファイル(%USERPROFILE%\.ssh\config)の作成
Host bitbucket.org
  User git
  port 22
  Hostname bitbucket.org
  IdentityFile ~/.ssh/bitbucket_rsa
  TCPKeepAlive yes
  IdentitiesOnly yes

 

SSHD起動

管理者コマンドプロンプトより、SSHDを起動します。

C:\>cd PSTools
C:\PSTools>psexec -s cmd.exe /c "cd "c:\openssh-win32" && sshd.exe"

PsExec v2.11 - Execute processes remotely
Copyright (C) 2001-2014 Mark Russinovich
Sysinternals - www.sysinternals.com

[Build Oct 13 2015 10:49:11]

 
SSHD起動後、ローカルマシンにて、

  • TeraTermで接続
  • 日本語が文字化けするので、TeraTermの設定を変更
    • 設定 > 端末
    • 漢字-受信、漢字-送信をともにSJISへと変更する

を行い、問題なく接続できることを確認します。

SSHDを停止する場合は、Ctrl + Cにて行います。

 

リモートマシンのAdministratorユーザーを有効化

Fabricでデプロイする場合、run()では一般ユーザーでの操作となるようです。そのため、そのままではIISの設定など管理者権限が必要なものが操作できません。

この場合、PsExecを使って管理者ユーザーを指定することで、管理者権限で操作ができるようになります。

そこで、リモートマシン上のローカルAdministratorユーザーを有効化します。

なお、ドメインユーザーでも試してみましたが、ローカルのAdministratorsグループに所属している場合でも、以下の影響か、「ユーザー名またはパスワードが正しくありません。」というエラーが表示されて実行できませんでした。
User Account Control (UAC) and PsExec - RioSec

 

ローカルマシンにて、Djangoアプリの作成

以下の内容で、Djangoアプリを作成します。

 

ひな型の取得

今回は、Djangoアプリのひな型としてthinkAmi-sandbox/Django_hello_worldを使います。

  • Download Zipボタンでローカルにダウンロード
  • ダウンロードしたzipファイルをC:\Django_wfastcgi_sample として展開
    • C:\Django_wfastcgi_sample\envC:\Django_wfastcgi_sample\my_projectな形で配置

 

virtualenv環境の用意
# Python2.7と3.4が入っているため、Python3.4を指定しておく
C:\Django_wfastcgi_sample>virtualenv env --python=C:\Python34\python.exe

C:\Django_wfastcgi_sample>env\Scripts\activate

(env) C:\Django_wfastcgi_sample>pip install django
...
Successfully installed django-1.8.6

(env) C:\Django_wfastcgi_sample>pip install wfastcgi
...
Successfully installed wfastcgi-2.2

 

web.configファイルの作成

今回は、C:\Django_wfastcgi_sample\my_project\web.config に、以下の内容で作成します。

<configuration>
  <appSettings>
    <add key="WSGI_HANDLER" value="django.core.wsgi.get_wsgi_application()" />
    <add key="PYTHONPATH" value="C:\django_apps\Django_wfastcgi_sample" />
    <add key="DJANGO_SETTINGS_MODULE" value="my_project.settings" />
  </appSettings>
  <system.webServer>
    <handlers>
        <add name="Python FastCGI" path="*" verb="*" modules="FastCgiModule" scriptProcessor="c:\django_apps\Django_wfastcgi_sample\env\scripts\python.exe|c:\django_apps\Django_wfastcgi_sample\env\lib\site-packages\wfastcgi.py" resourceType="Unspecified" />
    </handlers>
  </system.webServer>
</configuration>

 

requirement.txtの作成

C:\Django_wfastcgi_sample\my_project\requirements.txt として作成します。

(env) C:\Django_wfastcgi_sample>cd my_project

(env) C:\Django_wfastcgi_sample\my_project>pip freeze -l > requirements.txt

 

GitHubへpush

GUIでBitbucketにリポジトリ作成後に、以下の作業を行います。

# Gitリポジトリの作成
(env) C:\Django_wfastcgi_sample>git init
(env) C:\Django_wfastcgi_sample>git add .
(env) C:\Django_wfastcgi_sample>git commit -m "first commit"

# remoteにBitbucketを追加
(env) C:\Django_wfastcgi_sample>git remote add origin git@bitbucket.org:<USER_NAME>/django_wfastcgi_sample.git

# git push
(env) C:\Django_wfastcgi_sample>git push -u origin master
...
Branch master set up to track remote branch master from origin.

 

fabfile.py作成時の調査

fabfile.pyを作成するにあたり、いくつか悩んだことがあったので、メモとして残しておきます。

Win32-OpenSSHでのrunについて

Fabricのタスクの中で、

run("pip list")
run("pip install virtualenv")

のように連続してrunすると、

socket.error: [Errno 10054] 既存の接続はリモート ホストに強制的に切断されました。

と2回目のrun実行時にエラーが発生し、動作が中断されてしまいました。これは10_13_201511_09_2015のどちらのバージョンでも発生します。

一方、freeSSHdを使った場合には発生しませんでいた。

 
Win32-OpenSSHのコネクションまわりに何かあると考え、run()後にdisconnect_all()したところ、エラーが発生しませんでした。

ただ、run()ごとにdisconnect_all()するのは見た目がイマイチなので、disconnect_all()する以下のデコレータを作りました。

def disconnect(func):
    import functools
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(**kwargs)
        
        disconnect_all()
        
        return result
    return wrapper

 
これにより

@disconnect
def pip_list():
    return run("pip list")

のようにすれば、タスク実行後にdisconnect_all()が自動実行されるようになりました。

 
参考:[Python] 怖くない!デコレータ - くろのて

 

警告表示の回避

実行ログの中に

No handlers could be found for logger "paramiko.transport"

という表示があり、気になりました。

そのため、stackoverflowを参考にして、以下の方法で回避することができました。
python - No handlers could be found for logger paramiko - Stack Overflow

import paramiko
paramiko.util.log_to_file(r"filename.log")

 

コマンドラインからのIISのサービス開始/停止

関係するサービスは

のため、以下を参考にして停止します。
Web サーバーを開始または停止する (IIS 7) - TechNet

 

コマンドラインからのIISのDefault Web SiteのPhysicalPathの変更

以下を参考に、appcmdを使って変更しました。
アプリケーション コンテンツの物理パスを変更する (IIS 7) - TechNet

なお、appcmdについては、以下の記事も参考になりました。

 

フォルダの存在チェック

はじめはPythonコードでやろうと考えましたが、いろいろと面倒だったため、dirを使うことにしました。

 

Bitbucketのプライベートリポジトリからのclone

ローカルマシンからFabricを使った場合、SSH形式のgit cloneがエラーとなりました。また、run("ssh -T git@bitbucket.org")もエラーとなりました。

一方、Fabricを使わずにローカルマシンでgit cloneした場合は、SSH形式でも正常に動作しました。

SSHまわりの環境やコードをいろいろと調査すれば良いのかもしれませんが、時間の都合上、今回はhttpsのパスワード形式でのcloneをすることにしました。

参考:Githubにhttpsでgit cloneできない時やったこと - Qiita

 

virtualenv環境下での実行

Fabricのprefixを使い、

with prefix(path\to\Scripts\activate.bat):
    run("pip list")

のようにすれば、virtualenv環境下でpip listが実行されました。
参考:python - Activate a virtualenv via fabric as deploy user - Stack Overflow

 
なお、Windowsの場合、上記のブログにもある通りenv.shellPowerShellだとprefix()が使えません。

そのため、Windowsでvirtualenvを使う場合には、env.shell = "Cmd.exe /C"コマンドプロンプトで実行するしかないようです。

 

FabricでPsExecの引数として"を含む内容がうまく動作しない

IISDefault Web Siteに対する変更を行うため、

run(PsExec.exe -u Administrator -p {password} %windir%\system32\inetsrv\appcmd set vdir "Default Web Site/" /physicalPath:C:\django_apps\Django_wfastcgi_sample\my_project)

としましたが、Fabric経由だとうまく動作しませんでした。ダブルクオーテーションがうまく認識されないのか、パラメータが足りない旨のエラーが出ました。

そこで、今回はバッチファイルを作成して、Fabricからそれを叩くようにしました。

 
C:\Django_wfastcgi_sample\iis_default_web_site_settings.bat として、以下の内容で作成します。

%windir%\system32\inetsrv\appcmd set vdir "Default Web Site/" /physicalPath:C:\django_apps\Django_wfastcgi_sample\my_project

 
fabfile.pyでは、

run(PsExec.exe -u Administrator -p {password} C:\django_apps\django_wfastcgi_sample\iis_default_web_site_settings.bat)

のようにしてバッチファイルを実行すれば、設定が反映されました。

 

fabfile.pyの内容

長くなるため、GitHubへのリンクとしておきます。
Django_wfastcgi_sample/fabfile.py - thinkAmi-sandbox/Django_wfastcgi_sample

 
あとは、ローカルマシンにて

  • Python2.7のvirtualenv環境の用意
  • virtualenv環境にFabricのインストール
  • 上記のfabfile.pyを使って、fab deploy

を行うことで、Djangoアプリをデプロイすることができました。

 

参考

公式ドキュメント

英語版の他、日本語版も用意されていました。

 

ソースコード

全体のソースコードGitHubに上げておきました。
thinkAmi-sandbox/Django_wfastcgi_sample

fabfile.pyはfabricの下にあります。