前言
最近一段时间 Yogurt 在考虑代码写完之后更新到服务器的问题。原先的做法是写完代码之后,调试没问题了直接打个压缩包,然后通过 stfp 上传到云服务器,然后再部署运行。如下图流程:
看上去流程没啥问题,但其实部分流程理论上是不需要的——例如:打包压缩项目和解压项目。这两个环节只是为了将开发端的代码传递到服务器端而存在的。而且再加上上传,感觉被浪费的时间就有点多了。
正常来说,我们项目的更新基本都是增量的,很少说会从零开始覆盖更新。但通过打包压缩的方式,每次打包的都是完整的项目,随着开发时间的推移,项目文件也会变得越来越大,每次都要重新打包上传,花费的时间就非常多了。同时最重要的是,项目的版本也不太好管理。
以上还只是针对服务器不多的情况,如果需要多容器多设备做负载均衡,那么这套流程所花费的时间就得乘以部署服务器的数量了。一来每次的更新时效无法保证,二来万一更新的项目出了问题需要回滚版本时就十分的麻烦了。
因此,Yogurt 一直都在思考是否有更好的方式来部署项目,一来方便省心,二来又能保证时效。这两天通过查阅相关的资料和自己做了较多的测试以后,想到了一个解决方案。整理如下,以备后查。
开发环境及工具
开发端
信息 | 说明 |
---|---|
操作系统版本 | Microsoft Windows 10 专业工作站 10.0.19042 64位 |
开发工具 | Visual Studio Code |
服务器端
信息 | 说明 |
---|---|
操作系统版本 | Ubuntu 18.04 64位 |
Docker 版本 | 18.09.7 |
Python 版本 | Python 3.x |
工具
信息 | 说明 |
---|---|
仓库 | Gitee.com |
钩子 | WebHooks |
操作步骤
Step 1 创建项目文件夹
从部署的角度来说,最方便的管理方式就是把项目打包成一个 Docker 的镜像,方便后期分发。同时也可以通过 Docker 来相对直观的管理项目的运行情况。
mkdir 你的项目名称
Step 2 创建用于部署推送的仓库
这里 Yogurt 是为了方便区分开发环境和生产环境而单独设置的仓库,仅推送测试完毕的项目文件。自行在 Gitee.com 上创建即可。
Step 3 创建部署公钥
由于直接采用 http
的方式 clone
仓库需要账号密码登录,这对自动执行代码 pull
命令来说是非常不利的,因此需要选择采用 SSH
的方式来 pull
代码,进而则需要创建公钥了。
点击 +添加部署公钥
后,将进入下图所示页面
我们需要生成一个 SSH
公钥。这步操作不管在开发端 Windows 上生成和在服务器端 Ubuntu 上生成都是一样的,只要生成了我们就可以使用。这里以 Windows 为例。
ssh-keygen -t ed25519 -C "你的邮箱"
按照提示完成三次回车,即可生成 ssh key。
在 Windows 上,你可以在 C:\Users\你的主机名\.ssh
中找到生成的 公钥
和 私钥
。
Ubuntu 上可以在
/root/.ssh
中找到
不带后缀的为私钥,带 .pub
的为公钥。这时可以通过右键使用记事本打开 公钥
。
将公钥内容全部复制到 添加部署公钥
的页面中。
设置好标题,点击添加即可。添加完成后效果如下:
再通过 SSH 测试一下公钥的效果
ssh -T git@gitee.com
出现 successfully
字样则说明公钥生效了。
公钥和私钥一次生成可以在任何一台设备上使用,只要在 Gitee 上填写的公钥跟私钥是对应的即可。
Step 4 创建 WebHooks
同页面下,找到 WebHooks
选项。
点击 WebHooks
。这里可以添加很多个 Webhooks 接口,根据需要自行添加即可。
WebHooks 的原理是当仓库触发了指定事件时,将事件信息发送到我们自定义的 Url 上。例如我们 Push
了最新的代码到仓库,那么仓库接收到该事件后,将此事件执行成功的消息转发 Post
到我们自定义的 Url 上,我们根据接收到的消息来执行相应的操作。
值得注意的是,WebHooks 转发是不支持内网的,因此需要准备一个域名,或者将服务部署到云服务器中使用固定 IP 来访问,或者其他能够让 Gitee
Post
消息给你的途径。
这里是把这个流程提前说明了,实际上可以在部署镜像之后再来设置的。毕竟最终在哪一台服务器上接收 WebHooks 消息,才填写哪个 Url 的。
Step 5 编写 Dockerfile 前的准备
这里以一个前端项目为例。
Step 5-1 Pull 项目仓库
测试项目是使用 Node.js + Vue3.js 写的,将 Build
的 dist
文件内的所有文件 Push
到 Step 2
创建好的仓库中。
然后在 Step 1
创建好的项目文件夹中通过 SSH_URL
Git Clone
下来。
cd 你的项目文件夹
sudo git clone git@gitee.com:你的SSH_URL
然后将项目名称修改为 html
。
mv 你的仓库名称 html
这里 Yogurt 使用的是 Nginx 作为 Web 服务器,它的默认根目录是
/var/www/html
。我们要提前把项目放到这个根目录下,为了 Dokcerfile 好写一点,就提前将其命名为html
。
通过仓库拉取的目的有二。一是需要通过 git clone 来获取初始的项目;二是通过 git clone 来获取
.git
文件,后面自动执行git pull
命令就可以了。
Step 5-2 密钥
创建 .ssh
文件夹,把 Step 3
生成的 公钥
和 密钥
文件复制进去
cd 你的项目文件夹
mkdir .ssh
sudo cp /root/.ssh/id_ed25519 .
sudo cp /root/.ssh/id_ed25519.pub .
Step 5-3 ssh 配置
创建 ssh_config
文件,输入以下内容:
vim ssh_config
# This is the ssh client system-wide configuration file. See
# ssh_config(5) for more information. This file provides defaults for
# users, and the values can be changed in per-user configuration files
# or on the command line.
# Configuration data is parsed as follows:
# 1. command line options
# 2. user-specific file
# 3. system-wide file
# Any configuration value is only changed the first time it is set.
# Thus, host-specific definitions should be at the beginning of the
# configuration file, and defaults at the end.
# Site-wide defaults for some commonly used options. For a comprehensive
# list of available options, their meanings and defaults, please see the
# ssh_config(5) man page.
Host *
# ForwardAgent no
# ForwardX11 no
# ForwardX11Trusted yes
# PasswordAuthentication yes
# HostbasedAuthentication no
# GSSAPIAuthentication no
# GSSAPIDelegateCredentials no
# GSSAPIKeyExchange no
# GSSAPITrustDNS no
# BatchMode no
# CheckHostIP yes
# AddressFamily any
# ConnectTimeout 0
# StrictHostKeyChecking ask
# IdentityFile ~/.ssh/id_rsa
# IdentityFile ~/.ssh/id_dsa
# IdentityFile ~/.ssh/id_ecdsa
# IdentityFile ~/.ssh/id_ed25519
# Port 22
# Protocol 2
# Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
# MACs hmac-md5,hmac-sha1,umac-64@openssh.com
# EscapeChar ~
# Tunnel no
# TunnelDevice any:any
# PermitLocalCommand no
# VisualHostKey no
# ProxyCommand ssh -q -W %h:%p gateway.example.com
# RekeyLimit 1G 1h
SendEnv LANG LC_*
HashKnownHosts yes
GSSAPIAuthentication yes
# 主要是下面这个,其他的默认就可以了
StrictHostKeyChecking no
ssh_config
这份文件在 ubuntu 系统中是有的,可以直接复制出来,然后在末尾加上StrictHostKeyChecking no
就可以了。路径是/etc/ssh/ssh_config
。
因为一般来说由于安全性的考虑,首次连接 ssh 的时候都是需要手动输入 yes
确认密钥的,但是我们在 Docker 内部署好的程序是全自动运行的,不能被中断,因此需要提前关掉确认参数。
为了安全起见,一定要记得先复制出来再修改。
Step 5-4 Webhooks 服务
原理其实很简单,我们只要部署一个 Web 服务用于接收消息,然后执行 git pull
命令即可。这里 Yogurt 使用的是 python + flask + tornado。代码示例如下:
vim WebHooks.py
# -*- coding: utf-8 -*-
from flask import Flask
from flask import request
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from datetime import datetime
from os import popen
from os import path
webApp = Flask(__name__)
@webApp.route('/push', methods = [ 'POST' ])
def Push():
'''Gitee WebHooks
'''
'''维护参数'''
WEBHOOKS_PASSWORD = '你的 Webhooks 密码'
SSH_URL = '你的仓库 ssh_url'
FETCH_NAME = 'refs/heads/master'
'''执行部分'''
result = { 'state': '40000', 'message': '请求失败' }
headers = request.headers
data = request.json
# 校验请求头
headersCheck = {
'User-Agent': 'git-oschina-hook',
'X-Gitee-Token': WEBHOOKS_PASSWORD, # 在 gitee 中设置的 WebHook 密码
'X-Gitee-Event': 'Push Hook' # 只接受仓库推送钩子事件
}
itemCount = 0
for item in headers:
key, value = item
if key in headersCheck:
if headersCheck[key] != value:
result = {
'state': '40001',
'message': '请求头内容异常',
'datetime': str(datetime.now())
}
return result
itemCount += 1
if itemCount == 0:
result = {
'state': '40002',
'message': '请求头内容异常',
'datetime': str(datetime.now())
}
return result
# 只接受指定仓库的推送
if data['project']['ssh_url'] == SSH_URL:
# 只接受指定分支的推送
if data['ref'] == FETCH_NAME:
# 执行 shell 脚本更新代码
# 由于是通过 SSH 更新的仓库, 因此无需通过网址来拉取
cmdList = [
'cd {}'.format(path.abspath(path.dirname(__file__))),
'git pull'
]
cmd = ' && '.join(cmdList)
with popen(cmd, 'r') as process: execResult = process.read()
# 2021-12-04 14:08 Yogurt_cry 可以加上发一封邮件到指定邮箱
# 或者发到指定钉钉、企业微信或者同步推送到指定网站
# 目前此需求还不强烈, 到时用到再说
# 执行完毕后返回执行结果
result = {
'state': '20000',
'message': execResult,
'datetime': str(datetime.now())
}
return result
result['datetime'] = str(datetime.now())
return result
if __name__ == '__main__':
httpServer = HTTPServer(WSGIContainer(webApp))
httpServer.listen(5000)
IOLoop.instance().start()
很简单的处理逻辑,没啥复杂的,就不多说了。应该看代码就可以了。
反正只要实现接收功能就可以了,不必拘泥于使用什么语言。
Step 5-5 Docker 启动命令
由于 Docker 启动时需要同时启动两个服务,因此我们需要准备一个 .sh
文件来存放这些命令。
vim EntryPoint.py
#!/bin/bash
nohup python3 /var/www/html/WebHooks.py &
nginx -g 'daemon off;'
第一行是用于启动 WebHooks.py
的,为了方便执行 git pull
语句,就把这个文件放到了 html
目录下了。
第二行是为了启动 Nginx
。
Step 6 编写 Dockerfile
准备好 Step 5
的相关文件后,项目文件目录应该是长这样的:
你的项目
┝━ .ssh
│ ┝━ id_ed25519
│ ┕━ id_ed25519.pub
┝━ EntryPoint.sh
┝━ WebHooks.py
┝━ html
┕━ ssh_config
然后我们需要创建一个 Dockerfile
文件。
vim Dockerfile
# VERSION 0.0.1
# 基于 ubuntu 18.04 创建
FROM ubuntu:18.04
# 作者
MAINTAINER 你的名字 <你的邮箱>
# 基本环境配置
RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'Asia/Shanghai' >/etc/timezone && \
apt-get clean && \
apt-get update && \
# 安装必要软件
apt-get install -y python3 python3-pip nginx git && \
# 配置 pip
pip3 install -i https://pypi.mirrors.ustc.edu.cn/simple/ pip -U && \
# 安装 python 必要依赖
pip3 install -i https://pypi.mirrors.ustc.edu.cn/simple/ flask tornado && \
# 删除不必要的环境
rm -rf /var/cache/apk/* && \
rm -rf /var/lib/apt/lists/* && \
apt-get remove -y python3-pip && \
apt-get autoremove -y
# 复制 ssh 配置文件, 避免出现首次链接请求验证公钥的问题
COPY ssh_config /etc/ssh/ssh_config
# 复制 git 公钥和私钥文件
COPY .ssh /root/.ssh
# 修改 git 私钥权限
WORKDIR /root/.ssh
RUN chmod 700 id_ed25519
# 克隆前端项目包
WORKDIR /var/www
RUN rm -rf html
COPY html html
# 克隆 WebHooks.py
WORKDIR /var/www/html
COPY WebHooks.py WebHooks.py
# 克隆 EntryPoint.sh
WORKDIR /root
COPY EntryPoint.sh EntryPoint.sh
RUN chmod +x EntryPoint.sh
ENTRYPOINT ["/root/EntryPoint.sh"]
#CMD ["sh", "-c", "python3 /var/www/html/WebHooks.py && nginx -g 'daemon off;'"]
#CMD ["sh", "-c", "nginx -g 'daemon off;'"]
# Nginx 端口
EXPOSE 80
# Python 端口
EXPOSE 5000
Step 7 打包镜像
编写完 Dockerfile 后就可以创建镜像了。
cd 你的项目文件夹
sudo docker build -t="镜像名称" .
镜像名称只能小写
等待镜像构建结束即可。
Step 8 创建容器
sudo docker run -itd --name 容器名 -p 前端项目端口:80 -p Webhooks端口:5000 镜像名称
以上容器就部署完毕了,接下来的一些端口转发,服务器部署等等操作就根据实际情况来处理就可以了。记得把最后确定的 Webhooks Url 添加到 Gitee 上就好。
然后也可以自己注册一下 Docker 账号,把自己做好的 Docker 镜像上传到 Docker 官网上,部署多台设备的时候可以直接使用 docker pull 项目名:latest
来创建容器,省时省力。具体实操可自行查询相关大神的文献。
后记
希望以上能对跟 Yogurt 有相同需求的开发者有一丝帮助