Web應用隨時可能被攻擊者利用來奪取整個主機的權限,這是很常見也是很恐怖的一件事。為了更高的安全性,就需要將不同應用之間進行隔離(尤其是在這些應用屬于不同的用戶的情況下),然而這種隔離的實現一直是個挑戰。到目前為止,隔離性的實現方法已經有了很多,然而它們要么太過昂貴(時間的層面以及資源的層面),要么太過復雜(無論對開發者還是對管理員)。
本文將討論如何讓“容器化”的Python Web應用跑在安全的沙箱里,嚴格的堅守在其各自的環境中(當然,除非你指定它們與其他應用進行“連接”)。我將一步一步的介紹如何創建一個Docker容器,如何用這個容器來跑我們的Python Web應用,以及如何用一個Dockerfile來描述整個構建過程以實現完整的自動化。
Docker項目提供了一些可以搭配使用的上層工具,這些工具基于Linux內核的一些功能創建。整個項目的目標是幫助開發者和系統管理員門無痛的遷移應用(以及它們所涉及的所有依賴項),讓應用在各種系統和機器上都能歡快的跑起來。
實現這個目標的關鍵在于一個叫做docker容器的運行環境,這個環境實際上是一個具備安全屬性的LXC(Linux Containers)。容器的創建使用了Docker鏡像,Docker鏡像可以手動敲命令創建,也可以通過Dockerfiles實現自動化創建。
注:關于Docker的基礎知識(守護進程、CLI、鏡像等等),可參閱本系列的篇文章Docker Explained: Getting Started。
新版本的Docker(譯注:本文撰寫時間為2013年12月17日,當時的Docker新版本為0.7.1)可以在Ubuntu/Debian和CentOS/RHEL等多個Linux發行版上部署(你也可以使用DigitalOcean上現成的Docker鏡像,該鏡像基于Ubuntu 13.04創建)。
下面快速介紹一下Ubuntu上的安裝流程。
更新系統:
sudo aptitude update
sudo aptitude -y upgrade
檢查系統是否支持aufs:
sudo aptitude install linux-image-extra-`uname -r`
往apt-key添加Docker倉庫的密鑰(用于軟件包的驗證):
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
往aptitude軟件源添加Docker倉庫:
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
添加之后再更新一次系統:
sudo aptitude update
后,下載并安裝Docker:
sudo aptitude install lxc-docker
Ubuntu默認的防火墻(UFW)的默認設置是拒絕一切轉發(forwarding),但是Docker需要轉發,所以也需要設置一下UFW。
用nano編輯器打開UFW配置文件:
sudo nano /etc/default/ufw
找到DEFAULT_FORWARD_POLICY這一行,將
DEFAULT_FORWARD_POLICY="DROP"
替換為:
DEFAULT_FORWARD_POLICY="ACCEPT"
按 CTRL+X 再按 Y 鍵,保存退出。
后,把UFW重啟一下:
sudo ufw reload
在正式開始之前,我們還是先復習一下上次在基礎篇中介紹過的一些基本命令。
一般來說,Docker守護進程在你安裝完成了之后就已經在后臺運行,等待接收來自Docker命令行的指令。不過有的時候我們也需要手動啟動Docker守護進程:
sudo docker -d &
Docker命令行的基本語法如下:
sudo docker [option] [command] [arguments]
注:Docker的運行需要sudo權限。
下面列出了目前可用的Docker命令(譯注:可以參考InfoQ中文站文章深入淺出Docker(二):Docker命令行探秘):
attach 附著到一個運行的容器上
build 從一個Dockerfile建立鏡像
commit 將一個變更后的容器創建為一個新鏡像
cp 在容器和本地文件系統之間復制文件/目錄
create 創建一個新的容器
diff 檢測容器文件系統的變更
events 從服務器獲取實時事件
exec 在一個運行中的容器內執行命令
export 將一個容器的文件系統輸出為tar壓縮包
history 顯示一個鏡像的歷史
images 列出鏡像列表
import 從tarball壓縮包導入內容以創建一個文件系統鏡像
info 顯示系統信息
inspect 返回容器或鏡像的底層信息
kill 殺死一個運行中的容器
load 從一個tar壓縮包或STDIN加載一個鏡像
login 登入Docker注冊表(Docker registry)
logout 從Docker注冊表登出
logs 抓取一個容器的日志
network 管理Docker網絡
pause 暫停一個容器內的所有進程
port 列出該容器的所有端口映射或指定端口映射
ps 列出容器列表
pull 從注冊表拉取一個鏡像或倉庫
push 往注冊表推送一個鏡像或倉庫
rename 重命名容器
restart 重啟容器
rm 刪除一個或多個容器
rmi 刪除一個或多個鏡像
run 在一個新的容器中運行一條命令
save 將鏡像保存至tar壓縮包
search 在Docker Hub搜索鏡像
start 啟動一個或多個容器
stats 顯示容器資源使用情況的實時信息流
stop 停止一個運行中的容器
tag 向注冊表標記一個鏡像
top 顯示一個容器下運行的進程
unpause 恢復運行一個容器里所有被暫停的進程
update 更新容器的資源
version 顯示Docker版本信息
volume 管理Docker卷
wait 阻塞對指定容器的其他調用方法,直到容器停止后退出阻塞。
我們已經完成了Docker的安裝,也熟悉了基本的命令,現在可以為我們的Python WSGI應用創建Docker容器了。
注:本章介紹的方法主要是練習用,并不適合于生產環境。生產環境下適用的自動化流程將在后續章節中介紹。
Docker的run指令會基于Ubunt鏡像創建一個新的容器。接下來,我們要用 -t 標識給這個容器附著(attach)一個終端,并運行一個 bash 進程。
我們將暴露這個容器的80端口用于從外部訪問。以后在更加復雜的環境中,你可能需要給多個實例做負載均衡,把不同的容器“連接”起來,再用一個反向代理容器去訪問它們。
sudo docker run -i -t -p 80:80 ubuntu /bin/bash
注:運行這個命令時,Docker可能需要先下載一個Ubuntu鏡像,下載完畢后才創建新容器。
注意:你的終端會“附著”(attach)在新創建的容器上。要與容器分離并回到之前的終端訪問點,可以按 CTRL+P 接著 CTRL+Q 執行脫離操作。“附著”在一個Docker容器上,基本上相當于從一個VPS內部訪問另一個VPS。
從脫離的狀態想要回到附著的狀態,需要執行如下步驟:
sudo docker ps 列出所有運行中的容器
sudo docker attach [id] 完成當前終端到該容器的附著
注意:我們在容器內部做的一切操作都將僅限于在容器內部執行,對宿主機是完全沒有影響的。
要在容器內部署Python WSGI應用(以及我們所需要的工具),首先我們需要對應的軟件倉庫。然而在Docker默認的Ubuntu鏡像里并沒有提供倉庫(Docker的設計者認為這樣有利于保持事物的簡化),因此我們需要給我們這個打底鏡像添加Ubuntu的universe軟件倉庫:
echo "deb http://archive.ubuntu.com/ubuntu/ $(lsb_release -sc) main universe" >> /etc/apt/sources.list
更新一下軟件列表:
apt-get update
再給我們的容器安裝一些必要的工具:
apt-get install -y tar \
git \
curl \
nano \
wget \
dialog \
net-tools
build-essential
本文將用一個簡單的Flask應用作為示范。如果你用的是其他框架也沒關系,安裝部署的方法都是一樣的。
再提醒一次:以下所有的命令都是在容器內部執行的,不會影響到宿主機。你可以想象成自己在一個全新的VPS上進行操作。
安裝Python和pip:
# 安裝pip依賴:setuptools
apt-get install -y python python-dev python-distribute python-pip
安裝我們的應用之前,還是讓我們再確認一下所有的依賴都已經就緒。首先是我們的框架——Flask。
因為我們已經裝好了pip,所以就直接用pip來安裝Flask:
pip install flask
裝好了Flask,創建一個“my_application”文件夾:
mkdir my_application
cd my_application
注:如果你想直接部署自己的應用(而不是這里的示范應用),可以參看下面的“小貼士”部分。
我們的示范應用是一個單頁面的“Hello World” Flask應用。下面用nano來創建app.py:
nano app.py
把下面這些內容復制到新創建的文件里:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
按 CTRL+X 再按 Y 鍵,保存退出。
或者,你也可以使用“requirements.txt”來定義應用的依賴項(比如Flask)。我們還是用nano來創建文件:
nano requirements.txt
在文件里輸入你所有的依賴項(下面只列出兩個,如果你需要別的請自行添加):
flask
cherrypy
按 CTRL+X 再按 Y 鍵,保存退出。
注:你可以用pip來生成自定義的依賴項列表。具體的操作方法可以參考這篇Common Python Tools: Using virtualenv, Installing with Pip, and Managing Packages。
后,我們這個應用的文件組織結構是這樣的:
/my_application
|
|- requirements.txt # 描述依賴項的文件
|- /app # 應用模塊(你的應用應該在這個目錄下)
|- app.py # WSGI文件,里面應該包含“app”的實例名稱(callable)
|- server.py # 可選,用于運行應用服務器(CherryPy)
注:關于“server.py”,請參閱下面的章節“配置我們的Python WSGI應用”。
注意:上面這些應用的文件、目錄都是在容器內部創建的。如果你要在宿主機上自動化構建鏡像(這個過程將在下面有關Dockerfile的章節中介紹),則你的宿主機上放置Dockerfile的目錄下也需要同樣的文件結構。
上述步驟描述了在容器內創建應用目錄的過程。然而在真實場景下,我們往往需要從軟件倉庫拉取源代碼。
要把你的軟件倉庫復制到容器內部,有幾個方法可以實現。下面介紹其中的兩個:
# 方法1
# 用git下載源代碼
# 用法:git clone [源代碼所在的URL]
# 示范:
git clone https://github.com/mitsuhiko/flask/tree/master/examples/flaskr
# 方法2
# 下載源代碼壓縮文件
# 用法:wget [源代碼壓縮文件所在的URL]
# 示范:(用真實的URL替換掉下面這個假的)
wget http://www.github.com/example_usr/application/tarball/v.v.x
# 解壓縮文件
# 用法:tar vxzf [文件名 .tar (.gz)]
# 示范:(用真實的文件名替換掉下面這個假的)
tar vxzf application.tar.gz
# 用pip下載安裝應用依賴
# 下載 requirements.txt (可以用 pip freeze output 生成),再用pip全部安裝:
# 用法:curl [requirements.txt 文件的URL] | pip install -r -
# 示范:(用真實的URL替換掉下面這個假的)
curl http://www.github.com/example_usr/application/requirements.txt | pip install -r -
要運行這個應用,我們需要一個Web服務器。運行這個WSGI應用的Web服務器需要安裝在代碼所在的同一臺容器中,作為該Docker容器運行的進程。
注:我們在示范中將使用CherryPy自帶的HTTP Web服務器,這是一個比較簡單而且可以用在生產環境的選擇。你也可以用Gunicorn甚至uSWGI(可以讓它們跑在Nginx的后面),我們其他的教程中介紹過這種用法。
用pip下載安裝CherryPy:
pip install cherrypy
創建“server.py”,用于服務“app.py”里面的Web應用:
nano server.py
把下面的內容復制粘貼到server.py里:
# 導入應用的語法:
# from app import application
# 示范:
from app import app
# 導入 CherryPy
import cherrypy
if __name__ == '__main__':
# 掛載應用
cherrypy.tree.graft(app, "/")
# 從默認服務器上分離
cherrypy.server.unsubscribe()
# 實例化一個新的服務器對象
server = cherrypy._cpserver.Server()
# 配置該服務器對象
server.socket_host = "0.0.0.0"
server.socket_port = 80
server.thread_pool = 30
# SSL相關配置
# server.ssl_module = 'pyopenssl'
# server.ssl_certificate = 'ssl/certificate.crt'
# server.ssl_private_key = 'ssl/private.key'
# server.ssl_certificate_chain = 'ssl/bundle.crt'
# 訂閱這個服務器對象
server.subscribe()
# 啟動服務器引擎
cherrypy.engine.start()
cherrypy.engine.block()
完成!現在我們就有了一個“Docker化”的Python Web應用,安全的跑在自己專屬的沙箱里。只要輸入下面的一行命令,它就可以給成千上萬個客戶端請求提供服務:
python server.py
這是讓服務器在前臺運行的指令。按下 CTRL+C 終止運行。如果想在后臺運行服務器,可輸入下面的指令:
python server.py &
后臺運行的應用需要用進程管理器(比如htop)來終止運行(kill或stop)。
注:有關CherryPy上跑Python應用的配置,可參閱這篇教程:How to deploy Python WSGI apps Using CherryPy Web Server。
簡單的測試一下應用的運行狀態(以及端口的分配狀態):在瀏覽器中訪問 http://[容器所在的VPS的IP地址] ,應該能夠看到“Hello World!”。
上面簡單的說過,手動創建容器的這個方法并不適合用于生產環境的部署。生產環境里應該用Dockerfile進行構建流程自動化。
我們已經知道了如何在容器內部進行外部資源的下載和安裝,那么Dockerfile其實也是一樣的原理。一個Dockerfile定義了Docker要如何生成一個鏡像,這個鏡像可以直接用來跑我們的Python應用。
先了解一下Dockerfile的基本功能。
Dockerfile是一種腳本文件,其中包含了一系列順序執行的命令,Docker通過執行這些命令就可以創建一個新的Docker鏡像。這極大的方便了部署。
Dockerfile一般會先用 FROM 命令定義一個打底的鏡像,然后執行一系列的動作,動作全部執行完畢之后就形成了終的鏡像,并將完成的鏡像提交給宿主機。
使用:
# 在當前位置用Dockerfile創建一個鏡像
# 將生成的鏡像標記為 [name] (比如nginx)
# 示范:sudo docker build -t [name] .
sudo docker build -t nginx_img .
注:我們還有一篇專門介紹dockerfile的文章可供查閱:Docker Explained: Using Dockerfiles to Automate Building of Images
### Add
從宿主機復制文件到容器
### CMD
設置要執行或者要發給ENTRYPOINT的默認命令
### ENTRYPOINT
設置容器內默認要啟動的應用
### ENV
設置環境變量(key = value)
### EXPOSE
暴露一個端口
### FROM
設置打底鏡像(base)
### MAINTAINER
設置Dockerfile的作者/所有者信息
### RUN
執行一條命令并提交執行后的(容器)鏡像
### USER
設置從鏡像運行容器的用戶名
### VOLUME
從宿主機加載一個目錄給容器
### WORKDIR
設置CMD運行時所在的目錄
在當前路徑下用nano編輯器創建Dockerfile:
sudo nano Dockerfile
注:下面的內容需要按順序添加到Dockerfile中。
Dockerfile的基本項包括 FROM 原始鏡像(比如Ubuntu)以及維護者姓名 MAINTAINER:
############################################################
# 創建Python WSGI應用容器的Dockerfile
# 基于Ubuntu
############################################################
# 設置Ubuntu為打底鏡像
FROM ubuntu
# 文件作者/維護者
MAINTAINER Maintaner Name
# 添加軟件資源庫的URL
RUN echo "deb http://archive.ubuntu.com/ubuntu/ $(lsb_release -sc) main universe" >> /etc/apt/sources.list
# 更新資源列表
RUN apt-get update
RUN apt-get install -y tar git curl nano wget dialog net-tools build-essential
注:上述的有些工具可能你用不到,不過為了以防萬一還是都裝進來先。
一些Python需要的工具(pip)好還是都先裝起來。你的框架(WAF)和Web服務器(WAS)都需要有它們才能安裝。
RUN apt-get install -y python python-dev python-distribute python-pip
部署應用可以使用Docker的 ADD 命令直接復制源代碼,也可以用 REQUIREMENTS 文件來一步到位。
注:如果你打算用一個文件描述所有的代碼位置,可以參考下面的文件結構。
文件結構示范
/my_application
|
|- requirements.txt # 描述依賴項的文件
|- /app # 應用模塊(你的應用應該在這個目錄下)
|- app.py # WSGI文件,里面應該包含“app”的實例名稱(callable)
|- server.py # 可選,用于運行應用服務器(CherryPy)
這個文件結構的創建過程在前面的章節中已經介紹過,這里不再贅述。總之,以上述文件結構為例,則再給Dockerfile末尾添加如下內容,將源代碼復制到容器內:
ADD /my_application /my_application
如果源代碼是公網的Git倉庫,則可以使用如下內容:
RUN git clone [你的源碼倉庫URL]
接下來,再從 requirements.txt 導入所有的依賴項:
# 用pip來下載安裝 requirements.txt 里面的東西
RUN pip install -r /my_application/requirements.txt
# 暴露端口
EXPOSE 80
# 設置CMD運行的默認路徑
WORKDIR /my_application
# 要運行的默認命令
# 該命令在新容器創建時開始執行
# 比如啟動CherryPy來運行服務
CMD python server.py
現在,整個Dockerfile看起來應該是這樣的:
############################################################
# Dockerfile to build Python WSGI Application Containers
# Based on Ubuntu
############################################################
# Set the base image to Ubuntu
FROM ubuntu
# File Author / Maintainer
MAINTAINER Maintaner Name
# Add the application resources URL
RUN echo "deb http://archive.ubuntu.com/ubuntu/ $(lsb_release -sc) main universe" >> /etc/apt/sources.list
# Update the sources list
RUN apt-get update
# Install basic applications
RUN apt-get install -y tar git curl nano wget dialog net-tools build-essential
# Install Python and Basic Python Tools
RUN apt-get install -y python python-dev python-distribute python-pip
# Copy the application folder inside the container
ADD /my_application /my_application
# Get pip to download and install requirements:
RUN pip install -r /my_application/requirements.txt
# Expose ports
EXPOSE 80
# Set the default directory where CMD will execute
WORKDIR /my_application
# Set the default command to execute
# when creating a new container
# i.e. using CherryPy to serve the application
CMD python server.py
按 CTRL+X 再按 Y 鍵,保存退出。
在之前的基礎教程部分我們提到過,Dockerfile的工作方式利用到了 docker build 命令。
我們通過Dockerfile指示docker從包含源代碼的路徑復制內容到容器內部,因此在構建之前務必要確認Dockerfile與代碼路徑的相對位置。
這樣一個Docker鏡像可以快速創建起來一個可以運行我們的Python應用的容器,我們需要做的只是輸入這樣一行指令:
sudo docker build -t my_application_img .
我們把這個鏡像命名為 my_application_img 。為要從這個鏡像啟動一個新的容器,只需要輸入下面的命令:
sudo docker run -name my_application_instance -p 80:80 -i -t my_application_img
然后就可以在瀏覽器里輸入你VPS的IP地址,訪問應用了。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。