呓语 | 杨英明的个人博客

专注于c++、Python,欢迎交流

By

一个 Vue+Django 小型 web 应用的 Docker 化实践案例

前言

Docker 是一个开源的应用容器引擎,让开发者可以将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。———— from 阮一峰'博客

由于经常在部署应用上花费时间,前段时间学习了一下 Docker,其中踩了不少坑。这里分享一个 Vue 作前端,Django 作后端的小型 web 项目 Docker 化的实践案例以及一些心得。

ps:Docker 化这个说法不太严谨,容器化可能更好一些,但我用到的容器技术是 Docker,所以本文中暂且这么说。

技术栈

首先介绍一个这个小型 Web 应用,它的功能是提供抖音视频的无水印下载,只要从抖音里将分享链接复制到搜索框里,就可以解析出无水印视频的下载链接,并提供下载。演示网址在这:点我

技术栈

  • 前端:Nginx + Nodejs + Vue
  • 后端:uWSGI + Django

为什么要使用 Docker?

可以看到,要想部署好这个 web 应用,至少要先安装以上软件和框架,并且 vue 要经过 build 构建过程,Django 应用要配好对应的 python 版本,然后启动 uwsgi ,编写 nginx 配置文件,再启动 nginx…… 如果你在一台陌生的服务器上部署,以上过程可能还会面临各种各样未知的错误和依赖问题。可以想见,这么一个简单的 web 应用,如果不顺利的话,可能会花费你半个小时以上的时间。

这个时候 Docker 就可以闪亮登场了,将应用容器化之后,部署它花费的时间可以稳定在 5~10 分钟,而且这个过程大部分时间是在自动部署,不用盯着它。

部署时间大幅缩短(30min+ => 5~10min)以及部署过程更加简单,这就是使用 Docker 的优势。

如何将应用 Docker 化?

对于初学者来说,我接触到有两个可行的方法使用 Docker ,它们的优劣放到这里探讨一下:

一个是在简单镜像基础上,直接进入容器搭建环境,然后打包成镜像,最后 push 到镜像仓库(比如 Docker官方仓库)中。需要使用时,只要将镜像 pull 到本地启动容器就可以了。这个过程和使用 git 时十分类似。

这样做看起来简单直接,能做到“一次部署,多次使用”的理念,但它有两个缺点:

  • 一个镜像动辄几百M,体积太大,在云端和本地交互过程中不太方便
  • 用户不一定信任你的镜像

二是编写 Dockerfile 文件,用户在本地自己 build 镜像,然后启动成容器。这样做有两个好处:

  • 完全本地操作,无需和云端打交道(但你还是要联网的)。
  • 构建镜像的过程中做了什么,看 Dockerfile 文件一目了然,用户更放心。

所以在本案例中我采用编写 Dockerfile 的方式将应用 Docker 化, 实际上在 github 上很多开发者的应用都只会提供 Dockerfile,通常比较大型的应用才会在镜像仓库中提供官方镜像。

在这个小项目中,我将前端和后端进行了分离,它们的项目地址分别见:[ 前端 | 后端 ],也就是说接下来要编写两个 Dockerfile 文件分别指定前端和后端构建镜像的过程。下面分别介绍他们 Docker 化遇到的问题和解决方案。

前端 Docker 化

前端我采用 Nginx + Nodejs + Vue 架构。

由于项目的前端使用了 Vue,所以 Nodejs 是必不可少的,而 Nodejs 有现成的官方镜像,可以直接在 Dockerfile 中引入作为基础镜像层,省却了 自己安装的麻烦。Vue 使用了 vue-router + element-ui + axios 依赖,这些都可以用 npm install & build 一键搞定。

问题在于 Vue 项目打包之后生成的是一个静态网站,那么如何让它被容器外的程序访问呢?

一种可行的解决方案是在容器中运行一个 Nginx 服务器,让它开放端口并转发请求到静态网站。

假设容器暴露的是80端口,那么在容器中访问 http://localhost:80 就可以访问到静态网站,假设在容器外的宿主机映射了8070端口到容器的80端口,那么在宿主机中访问 http://localhost:8070 相当于访问容器中的静态网站。

外部宿主机的 Nginx 配置文件:

server {
    listen 80;
    server_name app.test.com;   # 指定 nginx 需要代理的域名
    location / {
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # 这一步是关键,指定域名转发到的内部服务
        proxy_pass http://localhost:8070;   
    }
}

Nginx 也有现成的官方镜像,可以直接在 Dockerfile 中引入。

现在我们已经想好了容器中应该包含哪些框架和软件,那么 Dockerfile 应该怎么写呢?和后端 Docker 化不同的是,我们现在要引入两个基础镜像层。查了下 Dockerfile 的资料,发现语法上是支持的两个 FROM 的。并且 Vue 官方还提供了一个 Nginx + Nodejs Dockerfile 示例,见这里:Dockerize Vue.js App

基于这些资料,Dockerfile 文件内容见下:

FROM node:10.14.1 as build-stage
MAINTAINER ElliotXX "https://github.com/elliotxx"

# 拷贝当前目录到镜像中
COPY . /root/douyin_front/
WORKDIR /root/douyin_front/

# 安装一些常用工具
RUN apt-get update
RUN apt-get install -y vim wget htop

# 安装语言支持
RUN apt-get install -y locales

# Ensure that we always use UTF-8 and with Canadian English locale
RUN locale-gen en_CA.UTF-8

# print()时在控制台显示中文
ENV PYTHONIOENCODING=utf-8

# support Chinese
ENV LC_ALL=en_CA.UTF-8
ENV LANG=en_CA.UTF-j
ENV LANGUAGE=en_CA.UTF-8

# install dependencies
RUN npm install

# build for production with minification
RUN npm run build

# 生产/上线阶段
FROM nginx:1.14.2 as production-stage
COPY --from=build-stage /root/douyin_front/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

前端容器运行后里面只有这些进程:

后端 Docker 化

后端我采用 uWSGI + Django 架构。

其中应用程序主体是用 python 的 web 框架 Django 实现,我的 python 版本是 python3.6。Django 原生的服务器由于性能太弱,所以我选择 uWSGI 作为网关接口,uWSGI 实现了 WSGI 协议,用于 Python 应用程序和 Web 服务器(比如 Nginx)之间进行通信。

uWSGI 相当于一个服务器,启动之后会开放端口,外部宿主机可以通过端口映射访问到这个服务。然后我们修改外部宿主机 Nginx 的配置文件,转发到这个端口就可以了。

比如容器中 http://localhost:8004 运行了 uWSGI 程序,我们在外部宿主机也用 8004 端口映射到容器的 8004 端口。然后在宿主机 Nginx 配置文件绑定域名,并且开放 8005 端口,转发到 8004 端口的 uWSGI 服务。这样访问域名 http://douyin.yangyingming.com:8005 相当于访问容器中的 Django 程序。

外部宿主机的 Nginx 配置文件:

server {
    listen 8005;
    server_name douyin.yangyingming.com;

    # charset
    charset utf-8;

    # max upload size
    client_max_body_size 75M;

    location / {
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:8004;
        uwsgi_read_timeout 120s;
    }
}

Dockerfile 文件内容见下:

FROM ubuntu:16.04
MAINTAINER ElliotXX "https://github.com/elliotxx"

# 拷贝当前目录到镜像中
WORKDIR /root/
COPY . /root/douyin_back/

# 拷贝 pip 配置文件
COPY ./docker/pip.conf .pip/pip.conf

# 安装python3.6必要的包,以及一些常用工具
RUN apt-get update
RUN apt-get install -y vim wget htop software-properties-common 

# 安装语言包
RUN apt-get install -y locales

# Ensure that we always use UTF-8 and with Canadian English locale
RUN locale-gen en_CA.UTF-8

# 安装python3.6 from 第三方
RUN add-apt-repository ppa:jonathonf/python-3.6
RUN apt-get update
RUN apt-get install -y python3.6 python3.6-dev

# 安装pip3
RUN wget https://bootstrap.pypa.io/get-pip.py
RUN python3.6 get-pip.py

# 和自带的3.5共存
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.5 1
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.6 2
RUN update-alternatives --config python

# 安装 django uwsgi 等项目依赖
WORKDIR /root/douyin_back
RUN apt-get install -y uwsgi
RUN apt-get install -y gcc build-essential make
RUN pip3 install -r requirements.txt

# 覆盖 wsgi 配置和启动脚本为 docker 版本
COPY ./docker/uwsgi.ini ./uwsgi.ini
COPY ./docker/uwsgiserver.sh ./uwsgiserver.sh

# print()时在控制台显示中文
ENV PYTHONIOENCODING=utf-8

# support Chinese
ENV LC_ALL=en_CA.UTF-8
ENV LANG=en_CA.UTF-8
ENV LANGUAGE=en_CA.UTF-8

# 暴露的端口
EXPOSE 8004

# 启动服务
CMD uwsgi --ini uwsgi.ini --module douyin_back.wsgi

前端容器运行后里面只有这些进程:

尾声

以上便是 Docker 化一个简单 web 应用的案例,本文中分享了这个过程中我的一些思考和心得。

当然作为一名 Docker 初学者,文中难免有些纰漏和局限之处,望见谅,也欢迎拍砖。

原创声明

转载请注明:呓语 » 一个 Vue+Django 小型 web 应用的 Docker 化实践案例