项目概述

本项目旨在通过 GitLab + Jenkins + Ansible 实现 Hexo 博客的一键自动化部署,并使用 Nginx + Keepalived 实现多节点高可用架构,提升部署效率和服务稳定性。

架构设计图

flowchart TB
    Developer[开发者] -->|Git Push| GitLab[GitLab]
    GitLab -->|Webhook| Jenkins[Jenkins]
    Jenkins -->|调用 Ansible| Cluster[Web Server Cluster<br>Nginx + Hexo]
    Cluster --> Master[Master]
    Cluster --> Backup[Backup]
    Master -->|Keepalived| VIP[VIP]
    Backup -->|Keepalived| VIP
    
    %% 样式定义
    classDef default fill:#f9f,stroke:#333,stroke-width:2px;
    classDef server fill:#9cf,stroke:#333,stroke-width:2px;
    classDef cluster fill:#fcf,stroke:#333,stroke-width:2px;
    
    %% 应用样式
    class GitLab,Jenkins,Master,Backup server;
    class Cluster cluster;

环境准备

主机规划

主机名 IP 环境 角色
Gitlab 192.168.100.116 Ubuntu22.04 LTS / 4h4g GitLab 18.0
Jenkins 192.168.100.114 Ubuntu22.04LTS / 2h2g Jenkins 2.504.2 + Ansible
web01 192.168.100.110 Centos7.9 / 1h1g Nginx + Keepalived(主)
web02 192.168.100.112 Centos7.9 /1h1g Nginx + Keepalived(备)
VIP 192.168.100.120 - 虚拟IP地址

目录规划

服务 目录 说明
Hexo /www/blog/ 静态网站根目录
Nginx /etc/nginx/ Nginx安装目录
Keepalived /etc/keepalived Keepalived安装目录
Ansible /etc/ansible/ Ansible工作目录

部署步骤

搭建 GitLab 仓库

信任 GitLab 的 GPG 公钥

1
curl -fsSL https://packages.gitlab.com/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/gitlab_gitlab-ce-archive-keyring.gpg

配置/etc/apt/sources.list.d/gitlab-ce.list

1
2
echo 'deb [signed-by=/usr/share/keyrings/gitlab_gitlab-ce-archive-keyring.gpg] https://mirrors.cernet.edu.cn/gitlab-ce/ubuntu jammy main
' | sudo tee /etc/apt/sources.list.d/gitlab-ce.list

更新源并安装 gitlab-ce

1
2
sudo apt-get update
sudo apt-get install gitlab-ce

镜像站参考配置:https://help.mirrors.cernet.edu.cn/gitlab-ce/

编辑配置文件/etc/gitlab/gitlab.rb,默认80端口未被占用可不修改,修改external_url,nginx[‘listen_port’]

加载配置,同时防火墙放行端口

1
2
sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart

查看初始密码,默认用户名为root,密码将在24h后失效

1
cat /etc/gitlab/initial_root_password

搭建 Jenkins + Ansible 环境

安装jdk环境

这里以openjdk为例,实际最好编译安装java环境

1
2
sudo apt update
sudo apt install fontconfig openjdk-17-jre

检查安装情况

添加jenkins官方仓库到包管理器

1
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/null

安装jenkins

1
2
sudo apt update
sudo apt install jenkins

查看安装情况

1
sudo systemctl status jenkins

配置开机自启动,并放行响应端口

1
2
sudo systemctl start jenkins
sudo systemctl enable jenkins

初次访问的时候会有个解锁密码

1
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

安装Ansible

  • Ubuntu
1
sudo apt install ansible

  • Centos
1
2
yum install -y epel-release	
yum install -y ansible

配置 Anisble

配置SSH免密连接

创建密钥对

分发公钥

1
2
ssh-copy-id sky@192.168.100.110
ssh-copy-id sky@192.168.100.112

测试免密连接是否配置成功

目录结构设计

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
ansible/
├── deploy_hexo.yml # Hexo博客部署playbook
├── deploy_ha.yml # 高可用架构部署playbook
├── jenkins-playbook.yml # Jenkins调用的playbook
├── Jenkinsfile # Jenkins流水线配置
├── inventory/
└── hosts # 主机清单
├── group_vars/
└── web.yml # Web服务器组变量
└── roles/
├── nginx/ # Nginx角色
├── tasks/
└── main.yml # Nginx任务
├── handlers/
└── main.yml # Nginx处理程序
├── templates/
├── nginx.conf.j2 # Nginx配置模板
└── default.conf.j2 # 默认站点配置
└── files/
└── index.html # 静态文件
└── keepalived/ # Keepalived角色
├── tasks/
└── main.yml # Keepalived任务
├── handlers/
└── main.yml # Keepalived处理程序
└── templates/
├── keepalived.conf.j2 # Keepalived配置模板
└── check_nginx.sh.j2 # Nginx检查脚本

配置主机清单

inventory/hosts文件中配置Web服务器组:

1
2
3
4
5
6
7
[web]
web01 ansible_host=192.168.100.110
web02 ansible_host=192.168.100.112

[all:vars]
ansible_user=root
ansible_connection=ssh

配置Nginx角色

Nginx角色通过Ansible自动化完成安装、配置和服务启动。主要任务包括:

  1. 添加Nginx官方仓库
  2. 安装Nginx
  3. 配置Nginx服务
  4. 启动并启用Nginx服务
  5. 配置防火墙规则

编辑roles/nginx/handlers/main.yml

1
2
3
4
5
6
7
8
9
10
---
- name: restart nginx
systemd:
name: nginx
state: restarted

- name: reload firewalld
systemd:
name: firewalld
state: reloaded

编辑roles/nginx/tasks/main.yml

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
---
# 添加Nginx官方仓库
- name: 添加Nginx仓库
yum_repository:
name: nginx
description: nginx repo
baseurl: http://nginx.org/packages/centos/7/$basearch/
gpgcheck: no
enabled: yes
when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'

# 安装Nginx
- name: 安装Nginx
yum:
name: nginx
state: present
when: ansible_distribution == 'CentOS'

# 确保Nginx服务目录存在
- name: 确保Nginx配置目录存在
file:
path: /etc/nginx/conf.d
state: directory
mode: '0755'

# 复制Nginx配置文件
- name: 复制Nginx配置文件
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: '0644'
notify: restart nginx

# 复制默认站点配置
- name: 复制默认站点配置
template:
src: default.conf.j2
dest: /etc/nginx/conf.d/default.conf
mode: '0644'
notify: restart nginx

# 启用并启动Nginx服务
- name: 启用Nginx服务
systemd:
name: nginx
enabled: yes
state: started

# 开放防火墙端口
- name: 开放HTTP防火墙端口
firewalld:
port: 80/tcp
permanent: yes
state: enabled
notify: reload firewalld
when: ansible_distribution == 'CentOS'

配置roles/nginx/templates/default.conf.j2

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
server_name localhost;
error_log /var/log/nginx/blog_error.log notice;
access_log /var/log/nginx/blog_access.log main;
location / {
root /www/blog;
index index.html index.htm;
}
}

配置roles/nginx/templates/nginx.conf.j2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
user  nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

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;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}

配置Keepalived角色

Keepalived角色实现Web服务器的高可用配置,主要任务包括:

  1. 安装Keepalived
  2. 配置Keepalived服务
  3. 配置Nginx服务监控脚本
  4. 配置虚拟IP
  5. 启动并启用Keepalived服务

编辑roles/keepalived/handlers/main.yml

1
2
3
4
5
---
- name: restart keepalived
systemd:
name: keepalived
state: restarted

编辑roles/keepalived/tasks/main.yml

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
---
# 安装Keepalived
- name: 安装Keepalived
yum:
name: keepalived
state: present
when: ansible_distribution == 'CentOS'

# 创建检查脚本目录
- name: 创建检查脚本目录
file:
path: /etc/keepalived/scripts
state: directory
mode: '0755'

# 复制Nginx检查脚本
- name: 复制Nginx检查脚本
template:
src: check_nginx.sh.j2
dest: /etc/keepalived/scripts/check_nginx.sh
mode: '0755'

# 配置Keepalived
- name: 配置Keepalived
template:
src: keepalived.conf.j2
dest: /etc/keepalived/keepalived.conf
mode: '0644'
notify: restart keepalived

# 启用并启动Keepalived服务
- name: 启用Keepalived服务
systemd:
name: keepalived
enabled: yes
state: started

编辑roles/keepalived/templates/check_nginx.sh.j2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# 检查Nginx是否运行

/usr/bin/systemctl status nginx | grep -q 'active (running)'

if [ $? -ne 0 ]; then
/usr/bin/systemctl restart nginx
# 如果无法重启,则退出keepalived
sleep 2
/usr/bin/systemctl status nginx | grep -q 'active (running)'
if [ $? -ne 0 ]; then
/usr/bin/systemctl stop keepalived
fi
fi

编辑roles/keepalived/templates/keepalived.conf.j2

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
global_defs {
router_id {{ inventory_hostname }}
script_user root
enable_script_security
}

vrrp_script check_nginx {
script "/etc/keepalived/scripts/check_nginx.sh"
interval 3
weight -2
fall 2
rise 1
}

vrrp_instance VI_1 {
{% if inventory_hostname == 'web01' %}
state MASTER
priority 100
{% else %}
state BACKUP
priority 90
{% endif %}
interface eth0
virtual_router_id 51
advert_int 1
authentication {
auth_type PASS
auth_pass hexo_blog
}
virtual_ipaddress {
192.168.100.120/24
}
track_script {
check_nginx
}
}

编写部署Hexo博客 playbook

编辑deploy_hexo.yml

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
---
- name: 部署Hexo博客
hosts: web
become: yes
vars:
hexo_blog_path: /www/blog

roles:
- nginx

tasks:
- name: 创建Hexo博客目录
file:
path: "{{ hexo_blog_path }}"
state: directory
mode: '0755'

- name: 清空原有博客内容
shell: "rm -rf {{ hexo_blog_path }}/*"
args:
warn: false

- name: 复制Hexo博客内容
copy:
src: "{{ hexo_blog_content_dir }}/"
dest: "{{ hexo_blog_path }}"
mode: '0644'
directory_mode: '0755'
when: hexo_blog_content_dir is defined

编写部署Keepalived高可用 playbook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
- name: 配置高可用Web服务器
hosts: web
become: yes

roles:
- keepalived

tasks:
- name: 确保防火墙允许Keepalived VRRP协议
firewalld:
rich_rule: 'rule protocol value="vrrp" accept'
permanent: yes
state: enabled
notify: reload firewalld
when: ansible_distribution == 'CentOS'

handlers:
- name: reload firewalld
systemd:
name: firewalld
state: reloaded

编写Jenkins playbook

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
---
- name: Jenkins调用的Hexo部署任务
hosts: web
become: yes
vars:
hexo_blog_path: /www/blog
hexo_blog_content_dir: "{{ jenkins_workspace }}/public"

roles:
- nginx

tasks:
- name: 创建Hexo博客目录
file:
path: "{{ hexo_blog_path }}"
state: directory
mode: '0755'

- name: 清空原有博客内容
shell: "rm -rf {{ hexo_blog_path }}/*"
args:
warn: false

- name: 复制Hexo博客内容
copy:
src: "{{ hexo_blog_content_dir }}/"
dest: "{{ hexo_blog_path }}"
mode: '0644'
directory_mode: '0755'
when: hexo_blog_content_dir is defined

配置Gitlab

创建项目

推送本地项目

创建SSH密钥对

1
ssh-keygen -t rsa

复制id_rsa.pub文件的内容,配置Gitlab SSH密钥

初始化项目并推送到Gitlab仓库

1
2
3
4
5
6
git init
git add .
git switch --create main
git commit -m "Initial commit"
git remote add origin http://192.168.100.116:1080/root/hexo-blog.git
git push -u origin main

配置Jenkins公钥

配置Webhooks

在Jenkins查看触发器配置,复制url,生成token

配置url,token,勾选推送事件,合并请求事件

关闭SSL认证(本地测试)

勾选允许本地网络请求

配置Jenkins

配置用户凭证

配置Jenkins的私钥

配置Gitlab凭证

配置成果如下:

配置 NodeJS

安装完相应的插件,配置NodeJS,别名取成node

新建流水线项目

配置触发器

勾选图示选项

配置流水线

编辑Jenkinsfile,需要放在网站源码的根目录

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
pipeline {
agent any

tools {
nodejs "node"
}

stages {
stage('Checkout') {
steps {
checkout scm
}
}

stage('Install Dependencies') {
steps {
sh 'npm install'
sh 'npm install hexo-cli -g'
}
}

stage('Build') {
steps {
sh 'hexo clean && hexo generate'
}
}

stage('Deploy') {
steps {
script {
def workspace = env.WORKSPACE
sh "ansible-playbook -i /etc/ansible/inventory/hosts /etc/ansible/jenkins-playbook.yml -e 'jenkins_workspace=${workspace}'"
sh "ansible-playbook -i /etc/ansible/inventory/hosts /etc/ansible/deploy_ha.yml"
}
}
}
}

post {
success {
echo '部署成功!'
}
failure {
echo '部署失败,请检查日志!'
}
}
}

部署 & 测试

推送事件测试

状态码返回200

Jenkins出现新的构建

部署成功

访问网站测试,状态码均200,正常