Please enable Javascript to view the contents

如何使用 Jenkins、Docker、GitLab 搭建 Django 自动化部署流程

 ·  ☕ 5 分钟

大公司的程序员,容易产生的错觉之一就是,误将平台能力当作自己的能力。在大团队,我们不应仅关注自己的一亩三分地,更需要了解平台的各个环节。一方面,有助于更好地利用平台相关特性,另一方面,也为了自我技术更好地成长。本文,介绍了如何使用 Jekins、Docker、GitLab 搭建 Django 自动化开发部署流程。相关工具都是开源、可以拿来即用的。

1. 开发流程

在生产环境,Web 应用采用的是 K8S 多实例部署,状态服务 MySQL、RabbitMQ 采用的是集群部署。同时,还搭建了监控和日志采集、检索等周边。

相比较于生产环境,这里的开发流程,我希望能尽量模拟生产环境,但也不需要太完善。毕竟,个人的时间和精力有限,当有需求时,逐步完善是一个不错的选择。

这里使用 GitLab 作为开发仓库,使用 Jenkins 作为自动化引擎,部署使用的是 Docker 镜像。

下面是一个简单的部署流程:

当满足触发条件时,Jenkins 就会自动从 GitLab 拉取代码,制作 Docker 镜像,最终在服务器上运行 Django 实例。这样基本就可以,模拟整个部署流程。

2. GitLab 配置

选用 GitLab 是因为,其允许创建私有仓库。

  • 创建仓库

首先得创建一个 GitLab 仓库,例如: ProjectA

  • 添加远程访问 SSH-KEY

在本地执行命令,生成远程访问需要的 SSH-KSY

1
ssh-keygen -o -t rsa -b 4096 -C "[email protected]"

https://gitlab.com/profile 页面,找到 【SSH Keys】,添加上面生产的 Key 值。

  • 生成个人仓库访问的 Token

https://gitlab.com/profile 页面,找到 【Access Tokens】,完善信息,点击生成 PersonToken。

3. Jenkins 配置

Jenkins 可以对外直接提供 API ,也支持插件扩展。对于熟悉 Java 的团队,Jenkins 具有很强的吸引力。使用 Jenkins ,可以满足 CI、CD 各种各样的需求。

这里的 Jenkines 主要用于部署服务。通过接收 GitLab 发送的提交信息,拉取最新的代码,执行脚本,完成部署。

  • 安装 GitLab 相关插件

这里需要使用到的 Jenkins 插件主要有:

  1. Gitlab Authentication plugin
  2. Gitlab Hook Plugin
  3. Gitlab Plugin

在 【Jenkins】-> 【插件管理】 里面,搜索并安装插件,重启 Jenkins 生效。

  • 添加 Gitlab 访问凭据

在 【Jenkins】-> 【凭据】-> 【系统】-> 【全局凭据 (unrestricted)】 中,【添加凭据】 ,类型选择 【Gitlab API token】,API token 即为在第二章节中生成的 PersonToken。

  • 新建流水线,配置仓库

创建【构建一个自由风格的软件项目】,如上图填入项目 ProjectA 的仓库地址。点击新增 SSH-Key 访问凭证。

  • 配置 Jenkins 触发规则

如上图,在 【Build Triggers】中,勾选 【Build when a change is pushed to GitLab. GitLab webhook URL:】,获取 GitLab Webhook 地址。点击【Advanced】,生成 Token。

  • GitLab 配置 Webhook

上一步中,获取到了两个值,GitLab Webhook 和 Token。

如上图,在 Gitlab 项目仓库 【Settings】-> 【Integrations】 中填入相关信息。如果你的 Webhook 不是 https 链接,还需要去掉 【Enable SSL verification】 勾选。

  • Jenkins 构建配置

构建配置实际上就是 Jenkins 拉取仓库代码之后,执行的脚本命令。这里直接执行项目下的 start.sh 脚本,即可。

4. Docker 镜像制作

使用 Docker 进行部署,有利于打包环境依赖,对服务进行水平扩展。在生产环境中,通常会采用多实例+集群的方式进行部署,以保障服务高可用。

这里主要使用 docker-compose 编译镜像,编排 Django 运行时需要的容器。

上图是整个仓库的目录结构,分为四个部分。

4.1 Django 项目代码

Django 使用的是默认目录结构,有两点需要注意:

  1. 通过环境变量,区分不同环境

在启动时,需要传入一个环境变量,提供给 Django 区分环境。在 settings.py 文件中:

1
2
3
4
if os.getenv('Env') == 'Production':
    DEBUG = False
else:
    DEBUG = True
  1. Django DEBUG=False 模式下,无法转发静态文件,需要配置 WhiteNoise
1
2
3
4
5
6
7
8
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfile')
# 
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)
WHITENOISE_STATIC_PREFIX = '/static/'
MIDDLEWARE.append('whitenoise.middleware.WhiteNoiseMiddleware')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage'

4.2 数据存放

通过卷的形式,将数据目录挂载到 Docker,可以保存运行状态,避免因容器重启而丢失数据。

4.3 镜像配置

Python 镜像,需要安装基本的依赖包。

Dockerfile 文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
FROM python:3.7-alpine

RUN apk update \
    && apk add --no-cache --virtual bash \
    && apk add gcc \
    && apk add musl-dev \
    && apk add linux-headers \
    && apk add jpeg-dev \
    && apk add zlib-dev \
    && apk add mariadb-dev \
    && apk add libffi-dev

COPY requirements.txt /requirements.txt
RUN pip install --upgrade pip \
    && pip install -r requirements.txt \
    && rm /usr/bin/mysql*

RUN mkdir /code
WORKDIR /code

requirements.txt 文件

1
2
3
4
5
6
7
8
9
django==2.1.2
gunicorn==19.9.0
mysqlclient==1.3.13
pymysql==0.9.2
whitenoise==4.1.2
celery==4.2.1
django-celery-results==1.0.4
django-celery-beat==1.3.0
redis==2.10.6

MySQL 镜像,用于提供 DB 访问服务。

Dockerfile 文件:

1
2
FROM mysql:5.7
COPY my.cnf /etc/mysql/conf.d/my.cnf

my.cnf 文件:

1
2
3
4
[mysqld]
character-set-server=utf8
[client]
default-character-set=utf8
  • 容器编排

这里复用了 Python 镜像,提供 django 和 celery 容器环境。Django 通过 gunicorn 启动。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
version: '2'
services:
  python:
    build: ./docker/python
    container_name: django
    ports:
      - 7900:7900
    volumes:
      - ./code:/code
    command: >
      bash -c "pip install -r requirements.txt
      && python manage.py migrate
      && python manage.py collectstatic --no-input
      && gunicorn news.wsgi -b 0.0.0.0:7900"
    environment:
      - Env=Production
    depends_on:
      - mysql
      - redis
      - rabbitmq
      - celery
      - mongo
    networks:
      - django-networks

  mysql:
    build: ./docker/mysql
    container_name: mysql
    ports:
      - 3306:3306
    volumes:
      - ./data/mysql:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=news
    networks:
      - django-networks

  redis:
    image: redis:latest
    container_name: redis
    expose:
      - "6379"
    networks:
      - django-networks
  
  rabbitmq:
    image: rabbitmq:3-management
    container_name: rabbitmq
    environment:
        - RABBITMQ_DEFAULT_USER=guest
        - RABBITMQ_DEFAULT_PASS=guest
    ports:
        - "5673:5673"
    networks:
      - django-networks

  celery:
    build: ./docker/python
    container_name: celery
    environment:
      - Env=Production
    depends_on:
        - rabbitmq
        - mysql
    volumes:
        - ./code:/code
    command: >
      bash -c "pip install -r requirements.txt
      && celery -A news.celery worker -l INFO
      && celery -A news.celery beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler"
    networks:
        - django-networks

  mongo:
    image: mongo:latest
    container_name: mongo
    ports:
      - "27018:27017"
    volumes:
      - ./data/mongo:/data/db
    networks:
      - django-networks

networks:
  django-networks:
    driver: "bridge"

start.sh 脚本,用于编译镜像,重启容器。

1
2
3
4
#!/bin/bash
docker-compose build
docker-compose stop
docker-compose up -d

5. 运行测试

向 Gitlab 仓库提交代码之后,Jenkins 流水线自动触发执行。

至此,通过 7900 端口就可以访问服务了。如果,需要绑定域名,新增一条 Nginx Server 配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
server {
     listen 80;
     server_name yourdomain.com;

     location / {
         proxy_pass http://127.0.0.1:7900;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     }
}

通过触发 Celery 后台任务,可以确定 Celery、RabbitMQ、MySQL 都能正常提供服务。

6. 与生成环境的对比

  • 高可用

高并发的关键是无状态,利用集群为状态提供高性能、高可用的服务。这里的 MySQL、RabbitMQ、Redis 都是单实例,生产环境需要采用集群部署。

另一方面,采用单机单实例部署是十分不可靠的,最好能使用多机多实例部署。

  • 运行日志

对于线上服务,日志是审计和排查错误的重要信息。使用 ELK + Filebeat 采集不同链路阶段、不同级别的日志,并将其按时间顺序串起来,十分有必要。

  • 运行隔离

生产环境的一台主机可能会运行很多实例,需要对每个实例使用的资源,CPU、内存、IO 等进行隔离,避免相互影响。

  • 服务注册

这里我们通过 Nginx 新增一条 Server 配置,来新增一个服务。这里可以,通过 Etcd + Confd 自动化这一流程,可以参考之前的一篇文章。 如果采用 K8S,借助于 Ingress 能实现类似效果。

  • 监控

生产环境,当然也少不了对各种服务状态的监控和告警。可以使用 Prometheus + Grafana 等开源监控工具,快速搭建监控系统。


微信公众号
作者
微信公众号