前端项目的Docker镜像打包
对读者的要求
- 掌握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;
}