對讀者的要求
- 掌握nginx基本用法
- 掌握Docker基礎用法
- Linux 命令行基本操作(Windows下則是掌握
git bash
這個工具)
簡介
所有的項目最終都要布署到線上才能對外提供服務,在布署方案上,之前主要采用git
拉取的方式,而現在則主要使用docker
直接啟動鏡像的方式,或者通過雲容器k8s
,aws ecs
啟動鏡像。不論哪一種方式,項目的Docker
鏡像制作都是必要的。后端為主的項目和純前端的Docker
鏡像制作流程是一樣的,但是在配置上后端會復雜一些。因此分開討論,本文只談前端項目的鏡像制作。
為方便討論,本文所指的前端項目,本文使用umijs
的腳手架創建的前端項目,用於演示下面的步驟。讀者使用相同的腳本架基本上直接拷貝代碼就能使用。
最后達到的效果應該是用戶在任何一台機器上,直接執行下面的命令,啟動鏡像就能完成布署:
docker run -p 8080:80 pheye/demo-app:latest
步驟
創建鏡像文件
在前端工程目錄下,創建Dockerfile
文件
FROM nginx:alpine
MAINTAINER LIUWENCAN <phenye@gmail.com>
RUN adduser -D -H -u 5000 -s /bin/sh www
RUN rm /etc/nginx/conf.d/default.conf
ADD scripts/nginx.conf /etc/nginx/
ADD scripts/app.conf /etc/nginx/sites-available/
ADD dist /var/www
VOLUME /var/www
CMD ["nginx"]
這里面會將dist
目錄、scripts/nginx.conf
、scripts/app.conf
這2個nginx
配置文件添加到鏡像中。
create-react-app
默認輸出的目錄叫build
,請改為dist
目錄
其中,dist
目錄是通過npm run build
生成的。而scripts/nginx.conf
是一個全局的nginx
,是很固定的,主要是做打開gzip壓縮、設置日志格式等等,與本教程關系不大,因此直接查看附錄將文件拷貝過去即可。
scripts/app.conf
才是應用的配置文件,直接見下面配置,特別需要注意的是/api
這一節,它對應到前端在調試時使用的反向代理配置,如果不需要可直接忽略。
server {
listen 80;
root /var/www;
location ~* .*\.(gif|jpg|jpeg|png|bmp|swf|js|css)$ {
expires 30d;
}
location / {
# 用於配合 browserHistory使用
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://172.16.3.100:9200;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
}
構建鏡像
docker build -t demo-app:latest .
測試鏡像
docker run -p 80:80 demo-app:latest
正常啟動后,在瀏覽器中輸入http://127.0.0.1/
確認下鏡像是否正常工作。
推送鏡像
完成測試以后,要將鏡像推送到公共的鏡像倉庫,然后在服務器上再拉取下來。一般來說,如果是可公開的,推送到Docker Hub
就完事了。
docker tag demo-app:latest pheye/demo-app:latest
docker push pheye/demo-app:latest
但是在公司里面,一般都是要推送到私有鏡像倉庫,可選擇阿里雲鏡像倉庫、AWS ECR
,或者在內網自建鏡像倉庫都行。本文以阿里雲為例演示推送
# 登陸阿里雲鏡像倉庫
username=${ALIYUN_REGISTRY_USERNAME}
pwd=${ALIYUN_REGISTRY_PASSWORD}
docker login --username=$username -p $pwd registry.cn-hangzhou.aliyuncs.com
# 修改TAG並推送
docker tag demo-app:latest registry.cn-hangzhou.aliyuncs.com/phenye/demo-app:latest
docker push registry.cn-hangzhou.aliyuncs.com/phenye/demo-app:latest
推送鏡像以后,再登陸其他的機器VPS,直接按照“簡介”中的方式,啟動鏡像應該能正常訪問。
優化流程
前面的示例,總是打包成demo-app:latest
,版本號總是latest
,這種方式在生產方式下一旦出問題是沒辦法回滾的。實際使用場景通常是跟CI/CD結合,以每個git commit
作為版本號,有問題的時候就回滾。因此構建和推送都要支持版本號,每次都手動敲命令很麻煩。可以增加兩個腳本優化這個流程:
優化構建
創建scripts/build.sh
文件:
#!/bin/sh
if [ $# -gt 1 ] ; then
docker build -t demo-app:$1 -t demo-app:latest .
else
docker build -t demo-app:latest .
fi
構建鏡像,要指定版本時,就執行如下命令:
./scripts/build.sh v1.0.0
優化推送
創建./scripts/push.sh
#!/bin/sh
pwd=${ALIYUN_REGISTRY_PASSWORD}
docker login --username=phenye -p $pwd registry.cn-hangzhou.aliyuncs.com
docker tag demo-app:latest registry.cn-hangzhou.aliyuncs.com/phenye/demo-app:latest
docker push registry.cn-hangzhou.aliyuncs.com/phenye/demo-app:latest
if [ $# -gt 0 ] ; then
tag=$1
docker tag demo-app:latest registry.cn-hangzhou.aliyuncs.com/phenye/demo-app:${tag}
docker push registry.cn-hangzhou.aliyuncs.com/phenye/demo-app:${tag}
fi
要在推送時指定版本號,就使用如下命令
./scripts/push.sh v1.0.0
附錄
工程目錄結構
為便於討論,列出工程目錄結構,方案在后續章節出現的文件,如果不知道它們應該放哪里,可參照這個工程目錄結構。
.
├── Dockerfile
├── dist/
├── pages/
│ ├── index.css
│ └── index.js
└── scripts/
├── app.conf
├── build.sh
├── nginx.conf
└── push.sh
nginx配置
nginx.conf
並不是必須的,它主要做開啟壓縮、配置日志等操作,是一些全局的配置。
user www;
worker_processes 4;
pid /run/nginx.pid;
daemon off;
events {
worker_connections 2048;
multi_accept on;
use epoll;
}
http {
server_tokens off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 600;
fastcgi_read_timeout 300;
types_hash_max_size 2048;
client_max_body_size 20M;
server_names_hash_bucket_size 256;
include /etc/nginx/mime.types;
default_type application/octet-stream;
gzip on;
gzip_disable "msie6";
gzip_min_length 1k;
gzip_comp_level 1;
gzip_vary on;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
log_format access '$http_x_forwarded_for $remote_addr [$time_local] "http://$host" "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" "$remote_user" ';
log_format main '{"@timestamp":"$time_iso8601",'
'"@source":"$server_addr",'
'"hostname":"$hostname",'
'"ip":"$http_x_forwarded_for",'
'"client":"$remote_addr",'
'"request_method":"$request_method",'
'"scheme":"$scheme",'
'"domain":"$server_name",'
'"referer":"$http_referer",'
'"request":"$request_uri",'
'"args":"$args",'
'"size":$body_bytes_sent,'
'"status": $status,'
'"responsetime":$request_time,'
'"upstreamtime":"$upstream_response_time",'
'"upstreamaddr":"$upstream_addr",'
'"http_user_agent":"$http_user_agent",'
'"https":"$https"'
'}';
access_log /dev/stdout main;
error_log /dev/stderr;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-available/*.conf;
open_file_cache off; # Disabled for issue 619
charset UTF-8;
}