Dockerfile搭建環境並打包應用
在上一章Docker構建nginx+uwsgi+flask鏡像(一)的學習中,我們學會用命令行一句一句在alpine環境中搭建nginx+uwsgi+flask服務,但這體現不了Docker為我們帶來的便利,而本章,我們將通過Dockerfile來制作基礎鏡像和打包應用,因此會有兩個Dockerfile文件。
我們先來寫第一個Dockerfile文件,這個文件負責搭建運行環境,運行環境需要包括:nginx、uwsgi、Python3:
# 配置基礎鏡像 FROM alpine:3.8 # 添加標簽說明 LABEL author="moshangguang" email="XXX@qq.com" purpose="nginx+uwsgi+Python3基礎鏡像" # 配置清華鏡像地址 RUN echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.8/main/" > /etc/apk/repositories # 更新升級軟件 RUN apk add --update --upgrade # 安裝軟件 RUN apk add --no-cache nginx python3 uwsgi uwsgi-python3 # 升級pip,這一步同時會在/usr/bin/目錄下生成pip可執行文件 RUN pip3 install --no-cache-dir --upgrade pip # 建立軟鏈接 RUN ln -s /usr/bin/python3 /usr/bin/python
上面的安裝軟件相比之前少了個vim,因為之前我們是在容器內部編輯配置文件,所以需要這個命令,但現在我們的配置文件都是預先在容器之外編輯好在通過docker拷貝到容器內部,所以也就無需安裝vim。另外建立軟連接時,我們只建立了從python到python3的軟連接,沒有建立pip到pip3的軟鏈接,因為/usr/bin/pip在安裝軟件時已經生成。
執行docker build命令,生成鏡像,執行docker images命令可以看到生成nginx_uwsgi_py3鏡像,標簽為alpine3.8:
[root@ docker]# docker build -t nginx_uwsgi_py3:alpine3.8 . Sending build context to Docker daemon 4.096 kB …… Successfully built 63be35fe36ca [root@ docker]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx_uwsgi_py3 alpine3.8 63be35fe36ca 5 minutes ago 60 MB
於是,我們完成了第一個Dockerfile文件,這個Dockerfile文件可以為我們搭建我們所需要的運行環境鏡像。
我們可以把構建好的基礎鏡像推送到我們的Docker hub倉庫,先用docker login登錄之后
[root@ docker]# docker login -u moshangguang -p 123456 Login Succeeded
tips:上面登錄密碼是假的哈。
然后為我們的鏡像打上tag,之后推送鏡像。
[root@ docker]# docker tag 687445ba4c7f moshangguang/nginx_uwsgi_py3:alpine3.8 [root@ docker]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE moshangguang/nginx_uwsgi_py3 alpine3.8 63be35fe36ca 22 minutes ago 60 MB nginx_uwsgi_py3 alpine3.8 63be35fe36ca 22 minutes ago 60 MB [root@iZj6c0zloujsauztjjhyhgZ docker]# docker push moshangguang/nginx_uwsgi_py3:alpine3.8 The push refers to a repository [docker.io/moshangguang/nginx_uwsgi_py3] …… alpine3.8: digest: sha256:412ec97c1c51dffeee6b924494bc size: 1154
有了基礎鏡像,我們就可以開始編寫我們的應用了,這里先給出我們應用的目錄結構(web_app在github上的地址):
[root@ docker]# tree web_app web_app ├── app │ ├── app.py │ ├── requirements.txt │ └── uwsgi.ini ├── Dockerfile └── nginx.conf 1 directory, 5 files
最外層的web_app目錄包含一個app目錄,和兩個文件,分別是Dockerfile、nginx.conf,注意,web_app下的Dockerfile文件和之前的Dockerfile文件不同,這里的Dockerfile文件是用來打包應用的。nginx.conf文件在打包應用時會拷貝到容器中,作為nginx啟動的配置。
app目錄下有三個文件,分別是:app.py、requirements.txt和uwsgi.ini。我們唯一不熟悉的就是requirements.txt,這個文件用來存放我們Python應用所需要的庫,如flask、flask_sqlalchemy等等。在打包應用時會執行pip命令讀取這個文件的內容,安裝我們所需要的庫。
這里,我們打印下requirements.txt的內容:
[root@ app]# cat requirements.txt flask flask_sqlalchemy
app.py文件也略做修改,新增兩個路由/hello和/world:
from flask import Flask app = Flask(__name__) @app.route('/hello') def hello(): return 'Hello!!!\n' @app.route('/world') def world(): return 'World!!!\n' @app.route('/') def hello_world(): return 'Hello World!!!\n' if __name__ == '__main__': app.run()
上一章中,我們在uwsgi.ini文件中將uwsgi-socket配置綁定到本機的9000端口,同時在nginx.conf文件中設置uwsgi_pass,將請求轉發到9000端口,這樣的做法顯得有些累贅,如果以后我不想用9000端口,意味着我需要改兩個地方。那么,有沒有辦法讓uwsgi自動獲取綁定到一個端口,而nginx.conf又能獲取到uwsgi所綁定的端口呢?肯定是有的:
uwsgi.ini
[uwsgi] uwsgi-socket = /tmp/uwsgi.sock chmod-socket = 777 callable = app plugin = python3 wsgi-file = app.py buffer-size = 65535 processes = %(%k * 2) threads = %(%k * 20) disable-logging = true
上面的uwsgi.ini文件中,我們不再將uwsgi-socket這個配置項綁定到特定的一個端口,而是指定了一個文件,這個文件是Unix套接字,即通過文件系統(而非網絡地址)進行尋址和訪問的套接字。配置uwsgi-socket之后,還需要配置chmod-socket,Unix socket是個文件,所以會受到Unix系統的權限限制,可以配置成660或者777,使得uwsgi客戶端能夠訪問這個Unix socket文件,這里配置為777。
這里新增兩個優化參數:processes和threads,分別是開啟的進程數和線程數,而%k是魔數變量,代表CPU核數,如果我們是雙核CPU,那這里的processes和threads分別為4和40,即有4個進程,每個進程有40個線程。disable-logging的意思一目了然,代表不記錄請求信息的日志,只記錄錯誤以及uwsgi內部消息到日志中。
最后,我們再來看下nginx.conf需要做改動的地方,其實也就是http模塊下的server:
server { listen 6666; charset utf-8; client_max_body_size 75M; location / { include uwsgi_params; uwsgi_pass unix:///tmp/uwsgi.sock; …… } }
其實改動的地方也只有一個uwsgi_pass,原先我們是直接綁定在9000端口上,而現在我們要指向uwsgi-socket所指向的Unix套接字。這樣,nginx就可以自動將請求轉發給uwsgi所監聽的套接字了。
這里給出nginx.conf全部的內容:
user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; worker_rlimit_nofile 20480; events { use epoll; worker_connections 20480; multi_accept on; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; #請求量級大建議關閉acccess_log #access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 300s; client_header_timeout 300s; client_body_timeout 300s; gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_types text/html application/javascript application/json; include /etc/nginx/conf.d/*.conf; server { listen 6666; charset utf-8; client_max_body_size 75M; location / { include uwsgi_params; uwsgi_pass unix:///tmp/uwsgi.sock; uwsgi_send_timeout 300; uwsgi_connect_timeout 300; uwsgi_read_timeout 300; } } }
最后,我們來看下用於打包應用的Dockerfile:
# 使用基礎鏡像庫 FROM moshangguang/nginx_uwsgi_py3:alpine3.8 # 創建工作路徑 RUN mkdir /app # 指定容器啟動時執行的命令都在app目錄下執行 WORKDIR /app # 替換nginx的配置 COPY nginx.conf /etc/nginx/nginx.conf # 將本地app目錄下的內容拷貝到容器的app目錄下 COPY ./app/ /app/ # pip讀取requirements.txt內容安裝所需的庫 RUN pip install -r /app/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple some-package --no-cache-dir # 啟動nginx和uwsgi ENTRYPOINT nginx -g "daemon on;" && uwsgi --ini /app/uwsgi.ini
上面的每一條命令都有注釋,這里就不再多作介紹了。
現在,讓我們來打包web_app應用吧!將工作目錄移到web_app目錄下,執行docker build命令,創建鏡像:
[root@ web_app]# docker build -t web_app . Sending build context to Docker daemon 24.58 kB …… Successfully built 88212eefb0b4
查看剛剛創建的web_app鏡像:
[root@ web_app]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE web_app latest 88212eefb0b4 About a minute ago 79.9 MB
根據鏡像啟動一個容器,容器內部的nginx監聽的是6666端口,而宿主機則用9999端口接收請求,再轉發到容器內部的6666端口:
[root@ web_app]# docker run -p 9999:6666 -d web_app a8cd1104dfc994637011ebd9dd9160d62eab64b1c9bb6ceb9266c092eb425452
這里,測試容器內的應用是否能正常處理用戶的請求:
到此為止,我們便完成了用Docker構建基礎鏡像,並打包應用了。