下图为我们的自动化部署流程
用户推送代码到 GitLab,GitLab 将配置好的代码更新事件(流水线)发送到 GitLab-Runner或其他CI/DI软件,CI/DI软件完成自动部署。
1.获取 GitLab,官方地址:https://packages.gitlab.com/gitlab/gitlab-ce,因为我使用的是 Centos8,所以我下载了 wget --content-disposition https://packages.gitlab.com/gitlab/gitlab-ce/packages/el/8/gitlab-ce-15.3.2-ce.0.el8.x86_64.rpm/download.rpm
2.安装 GitLab,使用命令 sudo yum install gitlab-ce-15.3.2-ce.0.el8.x86_64.rpm
,如果报缺少包,则缺哪个安哪个。
3.执行gitlab-ctl reconfigure
,重新配置 GitLab。
4.查看登陆密码(24小时后失效),使用命令cat /etc/gitlab/initial_root_password
5.在网页上打开GitLab,端口号默认为80,用户名默认为:root
6.修改密码:右上角->Prefrences->Password
7.修改语言:右上角->Prefrences->Prefrences->Localization->Language
git clone http://gitlab.example.com/gitlab-instance-7a3f9f21/xlogin.git
拉取项目前面我们讲过,GitLab-Runner 用于接收 GitLab 的更新事件,然后运行我们事先配置好的事件流程(流水线),如 build
->test
->deploy
等。由此可以看出 GitLab-Runner 只是一个执行器,它接收来自 GitLab 特定格式的元数据,然后按照配置好的执行策略(配置文件)来运行;这个配置文件默认为项目根目录下的.gitlab-ci.yml
文件。
yum install gitlab-runner
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
,指定了gitlab-runner的用户名和工作目录gitlab-runner start
gitlab-runner register
。如下图:Enter an executor:表示通过什么方式运行流水线中配置的 script,如果在本机运行编译部署等任务,则这里填写 shell;其他选项这里暂不做讨论。
注册令牌获取方式:菜单 -> 管理员 -> 管理中心 -> 概览 -> Runner -> 注册一个实例Runner
添加 clone_url 项(不配置通过git拉取的时候会报错)
gitlab-runner restart
如前所述,流水线是我们配置自动化部署的关键,用户将代码推送到仓库时,GitLab会自动的调用流水线,GitLab 会将流水线中配置的行为交由 GitLab-Runner 来执行。
---
variables:
MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository
-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN
-Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true
-DdeployAtEnd=true"
image: maven:3.3.9-jdk-8
cache:
paths:
- ".m2/repository"
verify:
stage: test
script:
- 'mvn $MAVEN_CLI_OPTS verify'
except:
variables:
- "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
deploy:
stage: deploy
script:
- if [ ! -f /etc/gitlab/ci_settings.xml ]; then echo "ci_settings 文件不存在,具体请看介绍:
https://docs.gitlab.com/ee/user/packages/maven_repository/index.html#create-maven-packages-with-gitlab-cicd
"; fi
- 'echo "部署到远程仓库"'
- 'mvn $MAVEN_CLI_OPTS deploy -s /etc/gitlab/ci_settings.xml'
- 'mvn package'
- 'echo "部署到远程机器"'
- 'python3 /root/deploy.py ${CI_PROJECT_DIR}/target/*.jar ${CI_PROJECT_TITLE} /root/config.yml'
- 'echo "部署成功"'
only:
variables:
- "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
分为验证和部署两个阶段,阶段一验证代码,阶段二分别部署代码到远程 maven 仓库和远程机器
这一步主要体现在 mvn $MAVEN_CLI_OPTS deploy -s /etc/gitlab/ci_settings.xml
,这里使用了 maven 插件,相应的 pom.xml 文件为:
上面的 project.distributionManagement.repository.id 和 ci_settings.xml 文件中的 servers.server.id 一一对应。ci_setttings.xml 文件和 .m2/settings.xml 文件类似,属于 maven 的配置文件。在 ci_settings.xml 文件中,我们配置了远程 maven 仓库的账号密码,由此 maven 可以将打包后的结果 push 到远程 maven 仓库。
ci_settings.xml文件内容为:
123456
前面用到的部署脚本deploy.py运行环境为:至少python3.8、PyYAML包,代码如下:
import yaml
import os
import sys
class Executor:
def __init__(self, argv: list):
self.file = argv[3]
self.argv = argv
def _read_yml(self, file, mode='r', encoding='utf8') -> dict:
"""
读取 yml 文件
:param file: 文件名
:param mode: 打开文件模式,默认为 'r'
:param encoding: 编码格式
:return: dict
"""
with open(file, mode, encoding=encoding) as f:
res = yaml.full_load(f)
return res
def _validate(self, data: dict) -> bool:
"""
验证数据
:param data: 数据
:return: bool
"""
params = self.argv
if len(params) <3:
return False
self.jar_path = params[1]
self.project_name = params[2]
return True
def _run_command(self,command: str) -> list:
"""
运行本地命令
:param command: 命令
:return: 结果
"""
with os.popen(command) as f:
strs = f.readlines()
return strs
def _run_remote_command(self, data: dict) -> None:
"""
运行远程命令
:param data: 包含url、identity_file、command、username
:return: None
"""
ip = str(data.get("url"))
identity_file = str(data.get("identity_file"))
username = str(data.get("username"))
command = str(data.get("command"))
if len(ip) == 0 | len(identity_file) == 0 | len(command) == 0 | len(username) == 0:
print("远程命令参数出错,【ip】:【%s】【identity_file】:【%s】【command】:【%s】" % (ip, identity_file, command))
return
remote_command = f"ssh -i {identity_file} {username}@{ip} '{command}'"
print("开始执行远程命令:%s" % remote_command)
strs = self._run_command(remote_command)
print("远程命令执行结果:【%s】" % strs)
print("远程命令【%s】执行结束." % remote_command)
def _ssh(self, data: dict) -> None:
"""
mode = ssh
:param data: 数据
:return: None
"""
print("模式:【ssh】")
has_command = str(data.get("has_command"))
run_command = str(data.get("command"))
if (has_command == "True") & len(run_command) > 0:
self._run_remote_command(data)
else:
print("命令未执行,请检查参数.")
def _scp(self, data: dict) -> None:
"""
mode = scp
:param data: 数据
:return: None
"""
print("模式:【scp】")
identity_file = data.get("identity_file")
jar_path = self.jar_path
username = data.get("username")
spliter = os.sep
project_name = self.project_name
ip = data.get("url")
base_dir = data.get("base_dir")
has_command = str(data.get("has_command"))
run_command = str(data.get("command"))
success = False
command = f"scp -i {identity_file} {jar_path} {username}@{ip}:{base_dir}{spliter}{project_name}{spliter} "
print("开始执行命令: %s" % command)
strs = self._run_command(command)
if len(strs) == 0:
success = True
print("命令执行成功")
else:
print("命令【%s】执行失败" % command)
print("错误信息: %s" % strs)
if success & (has_command == "True") & len(run_command) > 0:
self._run_remote_command(data)
def _sftp(self, data: dict) -> None:
"""
mode = sftp
:param data: 数据
:return: None
"""
print("模式:【sftp】")
print(data)
def execute(self) -> None:
"""
开始执行
:return: None
"""
data = self._read_yml(self.file)
if not self._validate(data):
print("配置文件:%s 格式错误" % self.file)
deploy = data.get('deploy')
# 找到当前项目需要部署机器
needs = dict()
for one_instance in deploy:
ins = deploy.get(one_instance)
if self.project_name == ins.get("project_name"):
needs[one_instance] = ins
print("%s 将被部署到 %s 台机器" % (self.project_name, str(len(needs))))
for one_instance in needs:
ins = needs.get(one_instance)
print("%s 部署到 %s" % (self.project_name, one_instance))
mode = ins.get('mode')
# 通过反射方式调用函数
mode = "_" + mode
# 检查是否有 mode 函数 或者 属性
if hasattr(self, mode):
getattr(self, mode)(ins)
else:
print("未知模式:%s: %s" % (one_instance, mode))
if __name__ == "__main__":
argv = sys.argv
executor = Executor(argv)
executor.execute()
部署脚本的配置文件config.yml如下:
deploy:
ins1:
## 项目名称,脚本里用项目名称来匹配远程机,一个项目可配置多个远程机,从而部署到多个环境
project_name: test
url: 123.123.123.123
username: root
## 私钥文件地址
identity_file: /root/xxxx.pem
## 模式 -- 只实现了 ssh 和 scp 模式,ssh 仅运行命令,scp 将jar包部署到远程机
mode: scp
## jar文件最终路径为 {base.dir}/{project_name}/
base_dir: /box/
## 是否运行命令
has_command: True
## 运行命令
command: sh /box/test/start.sh
至此,配置完毕。
dd if=/dev/zero of=/data/swap bs=512 count=8388616
mkswap /data/swap
swapon /data/swap
echo “/data/swap swap swap defaults 0 0” >> /etc/fstab
cat /proc/swaps
之后可以选择重新加载 gitlab:sudo gitlab-ctl reconfigure
通过 gitlab-runner 运行脚本使用的是前面配置的 gitlab-runner 用户角色,因为 gitlab-runner 在部署的时候使用了私钥文件,所以 gitlab-runner 用户角色必须有私钥文件的读权限。