构建网络感知CI/CD流水线:GitLab与Ansible实战
自动化已成为企业网络管理的重要组成部分。无论网络采用何种自动化方式,其CI/CD流水线都应具备网络感知能力。
在网络感知CI/CD流水线中,意味着在推送新配置时需要检查现网关键配置。了解如何构建网络自动化需求以进行现网状态检查,有助于确保每个变更都经过验证且可回滚。Ansible剧本、Python或GoLang代码可以验证现网状态,并使用GitLab、Jenkins或GitHub Actions等工具执行。
CI/CD操作流程
要使CI/CD流水线具备网络感知能力,网络团队必须执行以下步骤:
- 预检查:对现网执行初始查询,例如检查先前配置的关键接口是否正常运行
- 部署:向网络应用新配置
- 后检查:验证预检查作业最初检查的关键配置是否仍然有效
- 回滚:如果后检查失败,重新配置关键接口
环境准备
在构建网络感知CI/CD流水线之前,请确保已在机器上安装以下组件:
- Multipass:创建带有预安装Docker的Ubuntu Server VM
- Cisco路由器镜像:也接受其他供应商的路由器
- VS Code和Remote Secure Shell扩展:通过SSH访问VM目录
需要具备Linux基础知识,包括创建和更改目录、创建、重命名和复制文件。还需了解SSH主机密钥、如何生成或删除密钥以及Linux操作系统保存位置。此外,需要对Ansible、Docker和CI/CD流水线工具(如GitLab)有基本了解。
基础设施架构设置
创建VM
1
|
multipass launch --memory 16G --cpus 4 docker
|
配置SSH访问
获取VM IP地址并配置无密码SSH访问:
1
2
3
|
multipass shell docker
ip address show ens3 # 我的VM IP地址是192.168.64.7/24
exit
|
主机配置:
1
2
3
4
5
6
|
# macOS - 添加ssh配置到主机
sudo tee -a ~/.ssh/config <<EOF
Host docker
HostName 192.168.64.7
User ubuntu
EOF
|
部署路由器到Containerlab
安装Containerlab:
1
|
bash -c "$(curl -sL https://get.containerlab.dev)"
|
准备路由器目录并转换镜像:
1
2
3
4
5
6
7
|
mkdir -p ~/network-aware/router
cd ~/network-aware/router/
git clone https://github.com/hellt/vrnetlab.git
cp x86_64_crb_linux-adventerprisek9-ms.bin vrnetlab/cisco/iol/
mv vrnetlab/cisco/iol/x86_64_crb_linux-adventerprisek9-ms.bin vrnetlab/cisco/iol/cisco_iol-15.7.bin
sudo apt install make
make -C vrnetlab/cisco/iol/ docker-image
|
创建Containerlab拓扑文件并部署路由器:
1
2
3
4
5
6
7
8
|
name: cisco-iol
topology:
nodes:
R1:
kind: cisco_iol
image: vrnetlab/cisco_iol:15.7
links:
- endpoints: ["R1:Ethernet0/1", "R1:Ethernet0/2"]
|
1
2
|
containerlab deploy --topo cisco-iol.clab.yaml
containerlab inspect
|
构建和推送Ansible镜像
运行本地Docker Hub注册表:
1
|
docker run -d -p 5000:5000 --name registry registry:2
|
构建Ansible镜像并推送到本地Docker Hub:
1
2
3
4
5
6
7
8
9
10
11
12
|
FROM ubuntu:latest
RUN apt update && \
# 安装ansible
apt install -y software-properties-common && \
add-apt-repository -y --update ppa:ansible/ansible && \
apt install -y ansible && \
# 安装替代ssh实现
apt install python3-pip -y && \
pip3 install ansible-pylibssh --break-system-packages && \
# 安装git
apt install git -y && \
apt clean
|
1
2
|
docker build -t localhost:5000/ansible:latest .
docker push localhost:5000/ansible:latest
|
部署GitLab容器
准备GitLab安装目录:
1
2
3
|
mkdir ~/gitlab-docker
echo export GITLAB_HOME=~/gitlab-docker >> ~/.bashrc
source ~/.bashrc
|
创建GitLab容器(约需6分钟启动):
1
2
3
4
5
6
7
8
9
10
11
|
docker run --detach \
--hostname gitlab \
--env GITLAB_OMNIBUS_CONFIG="external_url 'http://gitlab'" \
--publish 443:443 --publish 80:80 \
--name gitlab \
--restart always \
--volume $GITLAB_HOME/config:/etc/gitlab \
--volume $GITLAB_HOME/logs:/var/log/gitlab \
--volume $GITLAB_HOME/data:/var/opt/gitlab \
--shm-size 512m \
gitlab/gitlab-ce:latest
|
获取GitLab凭据:
1
2
|
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
# 用户名 = root
|
配置GitLab
创建GitLab仓库
导航至 Project > New project > Create blank project:
- 项目名称 = containerlab-project
- 组 = root
- 可见性级别 = public
创建Linux运行器
在GitLab UI中导航至 Settings > CI/CD > Runners > Create project runner:
- 标签 = containerlab
- 超时 = 600
- 操作系统 = linux
在VM上注册令牌:
1
2
3
4
|
RUNNER_TOKEN=<token>
sudo gitlab-runner register \
--url http://gitlab \
--token $RUNNER_TOKEN
|
配置Docker执行器
获取GitLab容器IP地址:
1
|
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' gitlab
|
编辑GitLab配置:
1
2
3
|
[runners.docker]
extra_hosts = ["gitlab:172.17.0.2"]
network_mode = "host"
|
重启运行器:
1
|
sudo gitlab-runner restart
|
设置CI/CD流水线
创建GitLab流水线变量
获取路由器SSH主机密钥:
1
|
ssh-keyscan 172.20.20.2
|
在GitLab UI中导航至 Settings > CI/CD > Variable:
- 键 = KNOWN_HOSTS
- 值 = <你的主机文件输出>
创建GitLab作业脚本
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
|
stages:
- pre_check
- deploy
- post_check
- rollback
image: localhost:5000/ansible:latest
before_script:
- echo $KNOWN_HOSTS > ~/.ssh/known_hosts
pre_check:
stage: pre_check
tags:
- containerlab
script:
- ansible-playbook pre-check.yaml -i inventory.yaml
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
deploy:
stage: deploy
tags:
- containerlab
script:
- ansible-playbook deploy.yaml -i inventory.yaml
rules:
- if: $CI_COMMIT_BRANCH == 'main'
post_check:
stage: post_check
tags:
- containerlab
script:
- ansible-playbook pre-check.yaml -i inventory.yaml
rules:
- if: $CI_COMMIT_BRANCH == 'main'
rollback:
stage: rollback
when: on_failure
tags:
- containerlab
script:
- ansible-playbook rollback.yaml -i inventory.yaml
rules:
- if: $CI_COMMIT_BRANCH == 'main'
|
创建Ansible配置文件
变量文件:
1
2
3
|
ansible_username: admin
ansible_passwd: admin
ansible_host: 172.20.20.2
|
清单文件:
1
2
3
4
5
6
7
8
9
10
|
local:
hosts:
clab-cisco-iol-R1:
ansible_connection: network_cli
ansible_host: "{{ ansible_host }}"
ansible_user: "{{ ansible_username }}"
ansible_password: "{{ ansible_passwd }}"
ansible_network_os: cisco.ios.ios
ansible_become: true
ansible_become_method: enable
|
创建预检查和后检查剧本
预检查剧本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
- hosts: clab-cisco-iol-R1
vars_files:
- vars/credentials.yaml
tasks:
- name: Gathers all interfaces
cisco.ios.ios_facts:
gather_network_resources:
- interfaces
register: int_status
- name: Print the interface details
ansible.builtin.debug:
var: int_status.ansible_facts.ansible_network_resources.interfaces
- name: Fail if the critical interface is down
loop: "{{ int_status.ansible_facts.ansible_network_resources.interfaces }}"
when: item.enabled == false and item.name == "Ethernet0/1"
ansible.builtin.fail:
msg: "Interface {{ item.name }} is down!"
|
创建部署剧本
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
|
- hosts: clab-cisco-iol-R1
vars_files:
- vars/credentials.yaml
tasks:
- name: set interface IP address and mask
cisco.ios.ios_l3_interfaces:
config:
- ipv4:
- address: 10.1.2.20/24
name: Ethernet0/2
register: int_address
- name: Print the interface IP address
ansible.builtin.debug:
var: int_address
- name: set the interface status up
cisco.ios.ios_interfaces:
config:
- description: critical
enabled: false # 关闭接口
name: Ethernet0/1 # 此接口
register: int_status
- name: Print the interface status
ansible.builtin.debug:
var: int_status
|
创建回滚剧本
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
|
- hosts: clab-cisco-iol-R1
vars_files:
- vars/credentials.yaml
tasks:
- name: configure critical interface IP address and mask
cisco.ios.ios_l3_interfaces:
config:
- ipv4:
- address: 10.1.1.10/24
name: Ethernet0/1
register: int_address
- name: print the interface IP address
ansible.builtin.debug:
var: int_address
- name: enable the critical interface status
cisco.ios.ios_interfaces:
config:
- description: critical
enabled: true
name: Ethernet0/1
register: int_status
- name: print the interface status
ansible.builtin.debug:
var: int_status
|
观察GitLab仪表板上的作业
推送更改到新分支后,在仓库中导航到该分支并打开合并请求。观察预检查作业、部署作业、后检查作业和回滚作业的执行情况。
替代方案
如果网络工程团队尚未过渡到Python,Ansible仍然可以在流水线中执行一次性实时状态检查。一旦团队采用Python和pyATS,可以逐步将pyATS实现引入流水线,因为它是供应商无关的。
并非每个流水线都需要检查实时网络状态。相反,可以使用Batfish和Suzieq等开源工具通过设备快照执行预检查和后检查。