说在前面
因为和朋友一起搭网站用作技术实现的平台和个人博客,所以身为一个毕业两年的前端,想要完成前端-后端-运维等等一整套网站流程可以说是任重而道远,但是有目标才有动力,所以当我承担了项目运维这块的内容时就开始疯狂的学习,终于经过几十个视频的讲解和无数次百度之后终于搭建了较稳定的前后端分离的自动化部署项目!
因为前端自动化相对比较简单,所以我会先用前端的自动化做例子一点点讲解,最后再说一下docker+django+uwsgi+nginx的后台自动化部署是怎么实现的。
建议配合视频解说帮助快速理解!
本篇文章中用到的知识点有:
- gitlab
- gitlab-cicd
- gitlab-runner
- docker
- vue
- django
- nginx
- uwsgi
如果上面有你不知道的没听过的知识点,也不用担心,本篇文章都会作简单介绍!
项目整体构架
现在还看不懂上面的图?没关系!能看懂多少是多少,我们继续往下说。
初步的构想是代码库用的是gitlab的官方库,正常的代码管理和git一样,前端是再服务器上用docker创建一个容器,容器里面用nginx做代理运行前端项目,后端是用docker创建一个容器,nginx做代理,静态文件走nginx,动态请求代理到uWSGI,让uWSGI做动态请求的处理。
然后想要实现的是我本地代码上传,对应服务器的项目自动更新,实现自动部署。
docker
因为我这次部署到服务器是基于docker的,所以这里简单介绍一下docker。
简单来说docker就是可以创建虚拟机的东西,但它创建的虚拟机不是完整的,就是用最少的性能搭建一个完整的环境,比如你要搭建一个node环境,那么你只需要运行docker run -ti --name=my-node node
命令就能进入一个全新的node环境,它和你当前的主机是隔离的,就相当于运行了一个只能使用node的虚拟机。
docker有三个概念,一个叫镜像一个叫容器还有一个Dockerfile,我们怎么理解呢,我打个比方:
我们知道winbows的安装文件就叫一个镜像,我们在一台电脑上安装了这个镜像,那么我们就可以通过windows操作这台电脑,那么我们开机进入的系统就相当于一个容器,所以得出结论,镜像是不可更改的,只有安装后成了容器,我们才能对它进行操作。
那么我们回头再看下docker run -ti --name=my-node node
这句指令。
docker run
就相当于我们安装windows的操作
-ti
就相当于开机,进入windows桌面
--name=my-node
就相当于我们这台windows的名字叫my-node
node
就相当于windows的安装文件,这里docker会从dockerhub的库中获取node这个镜像
那么我们在当前widows进行了一些操作,比如安装了几个软件,我现在想重新打包成一个镜像给别人安装,那么别人安装的windows就自带了我这几个软件。那么在docker中想要实现这个就需要编写一个文件Dockerfile
,这个文件会基于某个镜像,执行一些操作,最后用docker build
打包成一个新的镜像,然后别人就可以用docker run
去执行这个镜像,形成一个新的容器。
先简单理解一下,想要详细了解docker的我这里也有一篇文档可以参考。
gitlab代码库
我这里用的是gitlab官方库:https://gitlab.com/
其实,gitlab是提供了本地代码库的,但是放在服务器上运行需要的性能会比较高,好像是至少2核4G,而我买的两个服务器都是1核2G,所以就没有部署本地代码库。
因为这块的操作是和git一模一样的,所以本地代码pull/push到gitlab代码库在这里不多做叙述。
gitlab-cicd
什么是CI/CD?
咱们先来看一下概念:
-
CI:持续集成(Continuous Integration)
在源代码变更后自动检测、拉取、构建和(在大多数情况下)进行单元测试的过程。
-
CD:持续交付(Continuous Delivery)
持续交付(CD)通常是指整个流程链(管道),它自动监测源代码变更并通过构建、测试、打包和相关操作运行它们以生成可部署的版本,基本上没有任何人为干预。
gitlab的CI/CD
看完概念我们来具体了解一下gitlab的CI/CD的实现。
上图是gitlab左侧导航CI/CD的二级菜单,这里我们主要看两个:
-
Pipelines
流水线,就是说我们每次的代码变更或者其他我们设定的情况下触发的一个任务组,这个任务组里面可能会有多个任务,每个任务都做着不同的事情,如:安装环境,打包,部署等等。
-
Jobs
顾名思义,这里的Jobs就是上面所说的任务,一个流水线下可以有多个任务,这些都取决于我们的需求。
创建CI/CD流水线
现在我们知道,每一次变更代码后会触发gitlab上我们自己定义的流水线(Pipelines),然后流水线中有会有一个或者多个任务。
那么怎么去创建一个流水线呢?
创建
首先,我们需要在项目根目录下创建一个叫.gitlab-ci.yml
的文件,如下图所示。
编写
这里我直接放一个已经写好的.gitlab-ci.yml
,然后我解读一下。
# 这里是docker镜像,说明我们的整个流水线是在docker的node:alpine容器里面完成的
# 不懂docker的可以简单理解为我们下面的所有任务是在一个有node环境的虚拟机里完成的
# 这个虚拟机的默认的目录就是我们当前的项目里
image: node:alpine
# 这里是我们自定义了一些流水线的阶段
stages:
- install
- build
- deploy
# 因为每个任务中所有操作产生的新的文件在进行到下一个任务时都会被清除
# 但有些我们不希望清楚,所以我们用到缓存,cache中定义的paths下的文件会被缓存到下一个任务中
cache:
key: modules-cache
paths:
- node_modules
- dist
# 这里是我们的第一个任务,它的名字叫job_install,这个名字是可以随便写的,也可以用中文
job_install:
stage: install # 这里代表我们当前的任务处于install阶段
tags:
- vue3 # 这里是当前任务的标签,标签是我们后面在gitlab-runner中定义的
script: # 每个任务都必须有script,顾名思义就是执行的语句
- npm install # 前面说的我们处于一个有node环境的虚拟机,那这句话就是在这个虚拟机的我们当前项目里执行npm install
# 这是我们的第二个任务,逻辑和上面的第一个任务都一样,就不做详细说明
job_build:
stage: build
tags:
- vue3
script:
- npm run build
# 这是我们的第三个任务,因为运行到这里项目的打包已经完成,我们即将用docker创建新的容器部署项目
job_deploy:
stage: deploy
image: docker # 因为这里我们用到docker指令所以要把node环境切换到docker
tags:
- vue3
script:
# 这里是通过我们项目根目录下的Dockerfile文件创建一个新的镜像
# 不懂可以理解成打包成一个安装包
- docker build -t rainbow-admin .
# 这里是查看当前的服务器上有没有正在运行或者存在我们之前运行过的项目容器,如果有删除了
- if [ $(docker ps -aq --filter name=rainbow-admin-main) ];then docker rm -f rainbow-admin-main;fi
# 这里是运行我们刚才创建的新的镜像
# 不懂得可以理解成安装我们刚才打包好的安装包,安装过后会把你的项目运行在nginx里面,外网就可以访问你的项目了
- docker run -d -p 80:80 --name=rainbow-admin-main rainbow-admin
上面说到了一个Dockerfile文件,这是docker中创建镜像的文件,很简单的一个文件,我们这边也简单看一下。
# 这里说明我们的基础镜像是nginx
# 如同上面所说,不懂的可以简单理解为我们下面的所有任务是在一个有nginx环境的虚拟机里完成的
FROM nginx
# nginx的默认访问目录是/usr/share/nginx/html
# 所以我们只要把打包好的dist复制到对应目录下就可以
COPY dist /usr/share/nginx/html
好了,说到这里我们在看一下前面的项目整体框架图,应该是还有个gitlab-runner没有提到,那么我们继续!
其实.gitlab-ci.yml
文件的编写规则很多还有CI/CD的一些其他运行方式等,我在这篇文档里面有展开介绍。
gitlab-runner
我们现在知道,我们在gitlab上的每一次代码变更都会触发CI/CD的流水线,那么这个流水线是在哪里执行的呢?
那我们就要提到gitlab-runner,它是执行在目标服务器上的一个进程,它通过token与对应项目关联,只要项目中的流水线触发,这边就会接收到,然后在当前服务器上执行部署任务。
如何创建gitlab-runner
首先,我这次使用的是docker环境下的gitlab-runner,当然runner还有很多其他创建方式,这里我就不多赘述了。
然后我们需要一台有docker环境的服务器,如果你问我怎么在centos的服务器上安装docker,我这里正好有个文档 你可以参考,只要按照上面的命令一条条执行就行。
好了docker环境也有了,我们正式开始部署gitlab-runner。
第一步,拉取并运行一个gitlab-runner镜像。
docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
等待安装运行完成,我们可以用docker ps
命令查看一下是不是有个如下图的东西。
如果有,那么恭喜你,可以继续了,如果没有,那么很遗憾,我也不知道什么原因,但是你可以百度。
现在我们只是安装运行了一个gitlab-runner,它还没有与我们的代码库关联起来,那么我们继续运行下面的命令。
docker exec -it gitlab-runner gitlab-runner register
执行完这句命令,会让我们输入几个东西(顺序不一定对,注意看提示):
- 第一个是域名,就是你代码库所在的域名,不知道的可以看下面的图
- 第二个是你项目的token,它在下面图中所示的位置
- 第三个是对当前runner的描述(随便写)
- 第四个是为当前runner添加标签,标签会在.gitlab-ci.yml文件中使用到(也是随便写)
- 第五个是runner运行的环境,我们这里填的是
docker
- 因为用的是docker所以这里要指定一个基础环境,因为是前端我就指定了一个
node:14
成功注册后如下图
这时候我们到gitlab设置cicd的token那个页面中刷新一下,看看是不是多了个runner。
好的,到这里我们已经完成了前端自动化部署的整个流程!
这时候我们可以修改代码上传,看看是否能触发流水线,服务器是否能完成流水线任务,访问服务器是否能看到前台页面。
常见问题
当然,如果失败了也不用担心,我可是尝试部署一百多次才完全成功的,期间各种小问题层出不穷,我这里说两个最常见的。
取消流水线的邮件提醒
这个邮件提醒有时候是真的烦,不想要的可以在右上角个人中心里面取消,设置如下图。
第一次创建.gitlab-ci.yml
后触发流水线提示没有权限问题
这个问题比较奇葩,网上找了很久才在外网找到问题所在,是因为默认启用了允许使用共享tags导致需要验证身份的问题。
这里我选择直接关闭,同样是在获取token那个页面。
Django环境的搭建
首先搭建Django的环境我们需要python的环境,根据项目的不同还可能有mysql的环境、nginx的环境等等。
我这里用到的有python3.8.8、nginx、uwsgi。
与前端不同,后端的环境很多,所以我们要先构建一个可以专门用于我们项目的docker镜像。
还有这里为什么用到nginx+uwsgi+django
的布局,我在这篇文档做了解释。
创建运行环境的docker镜像
这里我直接把我的Dockerfile贴出来,讲解一下我的思路。
# 这里是我的基础镜像,说明我是在centos里面创建环境的
FROM centos
# 这里是作者信息,可以忽略
MAINTAINER "JyQAQ"
# 这里是安装python需要用的相关环境,可以看到有很多,这里最后一个我顺便把nginx也安装了
RUN yum install -y zlib-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make wget libffi-devel mysql-devel nginx
# 这里从python的官网地址把我要的python版本下载下来
RUN wget https://www.python.org/ftp/python/3.8.8/Python-3.8.8.tgz
# 解压到/usr/local/src/
RUN tar -xzvf ./Python-3.8.8.tgz -C /usr/local/src/
# 这里我先创建了一个项目目录,就是我等下放项目文件的位置
RUN mkdir -p app/django_platform
# 设置当前工作目录,说明下面几句命令都是在这个目录下执行的
WORKDIR /usr/local/src/Python-3.8.8
# 检查安装包
RUN ./configure --prefix=/usr/local/python3
# 执行安装
RUN make && make install
# 设置python的环境变量
ENV PATH /usr/local/python3/bin:$PATH
# 重新设置工作目录,这里是centos安装的nginx的配置文件所在
WORKDIR /etc/nginx
# 把我自己编写的nginx配置覆盖默认的nginx
COPY django_platform.conf nginx.conf
# 把工作目录设置到我们项目的根目录
WORKDIR /opt/app/django_platform
# 因为我们只需要配置好环境就行,暂时不用把项目文件转移过来,所以把下面两句注释掉
#COPY . .
#RUN pip3 install -r requirements.txt
# 挂载项目目录,看不懂不用管
VOLUME /opt/app/django_platform
# 向外暴露80端口,因为nginx用到这个端口
EXPOSE 80
# 项目运行时执行的,默认启动bash命令行
CMD "/bin/bash"
上面的Dockerfile中用到了一个nginx配置django_platform.conf
,如下。
nginx的配置我在这里也不多赘述,我只说明几个我改的地方。
user nginx;
worker_processes auto;
error_log /opt/app/django_platform/nginx-error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
server_name django.rainbowinpaper.com;
root /usr/share/nginx/html;
# 默认的目录走的是Django的动态请求,我们需要代理到uwsgi
location / {
# uwsgi会用到的一个配置
include uwsgi_params;
uwsgi_connect_timeout 30;
# 连接到项目的uwsgi
uwsgi_pass unix:/opt/app/django_platform/uwsgi.sock;
}
# 静态文件用nginx代理就行
location /static/ {
# 指向django项目的静态文件目录
alias /opt/app/django_platform/static_all/;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
那么现在我们现在运行docker build -t jyqaq/rainbow-django .
打包成一个名叫jyqaq/rainbow-django的新镜像,然后我把它push到了dockerhub上,这样我就可以直接在Dockerfile中FROM jyqaq/rainbow-django
来使用这个镜像作为基础环境。
Django的.gitlab-ci.yml文件编写
环境已经搭建好了,那么剩下的就很简单了,不多bb,直接上代码。
# 基础环境是docker
image: docker
stages:
- install
- clear
# 这里的部署和前端的相同就不多赘述
部署环境:
stage: install
tags:
- django
script:
- docker build -t django_platform .
- if [ $(docker ps -aq --filter name=django_rainbow) ]; then docker rm -f django_rainbow;fi
- docker run -d -p 80:80 --name=django_rainbow django_platform
# 这里是因为流水线失败的话会产生废弃无用的镜像,这里清理一下,命令固定下面这些,也不赘述
清理docker:
stage: clear
tags:
- django
script:
- docker ps -a|grep "Exited" | awk '{print $1}' | xargs docker stop
- docker ps -a|grep "Exited" | awk '{print $1}' | xargs docker rm
- docker images|grep none|awk '{print $3}'|xargs docker rmi
注意点
特别说明一下,上面的基础环境是在docker里面,也就是说现在我们是在docker里面运行docker。
当然我们不想这样,我们想要的效果是用服务器上的docker去创建容器,所以我们需要去修改gitlab-runner的配置文件,回顾前面我们创建gitlab-runner的命令:
docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
可以看到我们把配置文件挂载到了服务器上的 /srv/gitlab-runner/config
目录下,那我们找到它,然后vi config.toml
编辑一下。
[runners.docker]
# 找到runners.docker配置的volumes,补上后面两个目录
volumes = ["/cache","/usr/bin/docker:/usr/bin/docker","/var/run/docker.sock:/var/run/docker.sock"]
上面部署用到了Dockerfile,这里贴一下。
FROM jyqaq/rainbow-django
MAINTAINER "JyQAQ"
# 把项目文件复制到容器里
COPY . .
# 下载django依赖
RUN pip3 install -r requirements.txt
# 容器运行时开启uwsgi和nginx,nginx -g "daemon off;"命令可以保持容器持续在后台运行
ENTRYPOINT uwsgi --ini /opt/app/django_platform/uwsgi.ini && nginx -g "daemon off;"
最后提醒一句,文章中用到的Dockerfile
、.gitlab-ci.yml
、django_platform.conf
都是放在项目根目录下的,不要放错位置就找不到了。
bingo,至此我们已经完成了前后端的自动化部署!