DevOps Lab Phase 1: 基于 Ansible 的多节点基础环境自动化

1. 阶段目标

本项目最终目标是搭建一套“小而全”的 DevOps 实验环境,后续会逐步加入:

  • Ansible
  • Docker
  • Kubernetes
  • Java / Spring Boot
  • Jenkins
  • CI/CD
  • ELK
  • 私有镜像仓库
  • 日志收集
  • 滚动发布
  • 故障排查
  • 版本回滚

Phase 1 不安装 Kubernetes,而是先解决最基础的问题:

虚拟机规划
    ↓
固定主机身份
    ↓
网络互通
    ↓
SSH 免密
    ↓
Ansible 控制
    ↓
统一 Linux 初始化
    ↓
K8s 节点内核初始化
    ↓
固定 IP
    ↓
Git / Gitee 保存项目

完成这一阶段后,后面的 Docker、Kubernetes 等软件就可以通过 Ansible 自动化安装,而不需要逐台 SSH 操作。

2. 实验环境规划

宿主机内存只有 16GB,因此不能给 5 台虚拟机都分配 4GB。

主机IP用户内存用途
k8s-master192.168.205.141master2GBKubernetes Control Plane
k8s-node1192.168.205.142node12GBKubernetes Worker
k8s-node2192.168.205.143node22GBKubernetes Worker
devops192.168.205.144devops2GBAnsible / 后续 Jenkins、Registry
elk192.168.205.145elk4GBElasticsearch / Logstash / Kibana
  • 操作系统: Ubuntu 20.04
  • 网络: 网段 192.168.205.0/24,网关 192.168.205.2
  • 网卡: ens33
  • 网络管理: NetworkManager

3. 确认虚拟机网络

首先在每台机器查看网卡和 IP:

ip -br addr

例如 ELK:

lo               UNKNOWN        127.0.0.1/8
ens33            UP             192.168.205.145/24

查看路由:

ip route

得到类似:

default via 192.168.205.2 dev ens33
192.168.205.0/24 dev ens33 ...

此时发现地址原本来自 DHCP,因此后面还需要转换为静态 IP。

4. 设置五台机器的 hostname

在 Master:

sudo hostnamectl set-hostname k8s-master

在 Node1:

sudo hostnamectl set-hostname k8s-node1

在 Node2:

sudo hostnamectl set-hostname k8s-node2

在 DevOps:

sudo hostnamectl set-hostname devops

在 ELK:

sudo hostnamectl set-hostname elk

5. 测试虚拟机之间的网络

在 devops 上测试其他节点:

ping -c 2 192.168.205.141
ping -c 2 192.168.205.142
ping -c 2 192.168.205.143
ping -c 2 192.168.205.145

测试 VMware 网关:

ping -c 2 192.168.205.2

测试外网:

ping -c 2 8.8.8.8

测试 DNS:

ping -c 2 www.baidu.com

6. 把 devops 作为 Ansible 控制节点

在 devops 上安装 Ansible:

sudo apt update
sudo apt install -y ansible openssh-client vim git

检查版本:

ansible --version

当前实际版本:

Ansible 2.9.6
Python 3.8.2

7. 配置 SSH 免密登录

在 devops 生成 SSH Key:

ssh-keygen -t ed25519

复制公钥到各节点:

ssh-copy-id master@192.168.205.141
ssh-copy-id node1@192.168.205.142
ssh-copy-id node2@192.168.205.143
ssh-copy-id elk@192.168.205.145

测试:

ssh master@192.168.205.141 hostname
ssh node1@192.168.205.142 hostname
ssh node2@192.168.205.143 hostname
ssh elk@192.168.205.145 hostname

8. 创建 DevOps Lab 项目目录

mkdir -p ~/devops-lab/ansible/{inventory,scripts}
cd ~/devops-lab/ansible

当前目录规划:

devops-lab/
└── ansible/
    ├── ansible.cfg
    ├── init.yml
    ├── network.yml
    ├── inventory/
    │   └── hosts.ini
    └── scripts/
        └── init.sh

9. 编写 Ansible Inventory

创建 inventory/hosts.ini

[k8s_control_plane]
k8s-master ansible_host=192.168.205.141 ansible_user=master

[k8s_workers]
k8s-node1 ansible_host=192.168.205.142 ansible_user=node1
k8s-node2 ansible_host=192.168.205.143 ansible_user=node2

[k8s_nodes:children]
k8s_control_plane
k8s_workers

[devops_servers]
devops ansible_host=192.168.205.144 ansible_connection=local

[elk_servers]
elk ansible_host=192.168.205.145 ansible_user=elk

10. 配置 ansible.cfg

创建 ansible.cfg

[defaults]
inventory = inventory/hosts.ini
host_key_checking = False

11. 第一次测试 Ansible

ansible all -m ping

12. 验证 sudo 权限

ansible all -b -m command -a "whoami"

13. 编写统一初始化脚本 init.sh

创建 scripts/init.sh 详情见代码仓库
devops-lab

https://gitee.com/knight-puno/devops-lab

14. init.sh 做了什么

所有节点都会统一完成:

  • 设置 hostname
  • 安装基础工具
  • 启用 SSH
  • 启用 chrony
  • 配置 /etc/hosts

15. Kubernetes 节点额外初始化

对于 k8s-masterk8s-node1k8s-node2,脚本还会做以下准备:

  • 关闭 Swap
  • 加载 overlay
  • 加载 br_netfilter
  • 开启 IPv4 Forwarding
操作作用不做会怎样
关闭 Swap让 Kubelet 准确判断节点真实可用内存Kubelet 启动报错,集群初始化失败
加载 overlay提供 OverlayFS 内核支持容器运行时无法正常工作
加载 br_netfilter让 Linux bridge 流量经过 iptablesService 负载均衡失效
开启 IPv4 转发允许 Linux 内核转发数据包CNI 插件报错,Pod 无法通信

16. 编写 init.yml

创建 init.yml

---
- hosts: k8s_nodes
  tasks:
    - name: Initialize k8s nodes
      script: "./scripts/init.sh {{ ansible_hostname }} k8s"

- hosts: devops_servers
  tasks:
    - name: Initialize devops server
      script: "./scripts/init.sh {{ ansible_hostname }} common"

- hosts: elk_servers
  tasks:
    - name: Initialize elk server
      script: "./scripts/init.sh {{ ansible_hostname }} common"

17. Ansible 2.9 的 script 兼容问题

将 script 模块写成 free-form 形式以避免兼容性问题。

18. Playbook 语法检查

ansible-playbook --syntax-check init.yml

19. 先灰度测试一个 Worker

ansible-playbook init.yml --limit k8s-node1

20. 验收 k8s-node1

ansible k8s-node1 -a "hostname"
ansible k8s-node1 -a "swapon --show"
ansible k8s-node1 -a "sysctl net.ipv4.ip_forward"
ansible k8s-node1 -m shell -a "lsmod | grep -E 'overlay|br_netfilter'"
ansible k8s-node1 -a "getent hosts k8s-master"

21. 初始化全部服务器

ansible-playbook init.yml

22. 批量检查服务器状态

ansible all -a "hostname"
ansible all -a "free -h"

23. K8s 三节点综合检查

ansible k8s_nodes -m shell -a 'echo "===$(hostname)==="; swapon --show; sysctl net.ipv4.ip_forward; lsmod | grep -E "overlay|br_netfilter"'

24. 为什么还需要固定 IP

Kubernetes 尤其是 Control Plane 不适合随意更换 IP,因此需要将 5 台全部改为静态地址。

25. 确认网络由 NetworkManager 管理

systemctl is-active NetworkManager
nmcli device status

26. 创建 network.yml

创建 network.yml,该 Playbook 负责把当前 DHCP 地址转换为静态 IP,IP 直接从 Inventory 的 ansible_host 变量读取。

27. 为什么 network.yml 使用 serial: 1

修改服务器网络属于高风险操作,使用 serial: 1 确保一次只修改一台,避免整个环境同时断开。

28. network.yml 中遇到的 devops IP 问题

必须在 Inventory 中为 devops 主机显式设置 ansible_host=192.168.205.144,否则变量解析会出错。

29. 查看 ansible_host

ansible all -m debug -a "var=ansible_host"

30. 先灰度测试静态 IP

ansible-playbook network.yml --limit k8s-node1

31. 验证静态 IP

ansible k8s-node1 -m ping
ansible k8s-node1 -m shell -a "ip -4 -br addr show ens33"
ansible k8s-node1 -m shell -a "ip route | grep default"

32. 测试静态网络的 Internet 与 DNS

ping -c 2 192.168.205.2
ping -c 2 8.8.8.8
getent hosts www.baidu.com

33. 批量配置静态 IP

# 排除 devops
ansible-playbook network.yml --limit 'all:!devops'
# 最后单独操作 devops
ansible-playbook network.yml --limit devops

34. 最终静态 IP

主机IP子网掩码
k8s-master192.168.205.141/24
k8s-node1192.168.205.142/24
k8s-node2192.168.205.143/24
devops192.168.205.144/24
elk192.168.205.145/24
  • 网关: 192.168.205.2
  • DNS: 192.168.205.2, 8.8.8.8
  • NetworkManager: ipv4.method=manual

35. 当前仓库文件

devops-lab/
├── README.md
├── .gitignore
├── docs/
│   └── phase1.md
└── ansible/
    ├── ansible.cfg
    ├── init.yml
    ├── network.yml
    ├── inventory/
    │   └── hosts.ini
    └── scripts/
        └── init.sh

36. 将项目纳入 Git

cd ~/devops-lab
git init
git add .
git commit -m "feat: complete phase1 infrastructure initialization"

37. 关联 Gitee

git remote add origin https://gitee.com/knight-puno/devops-lab.git
git branch -M master
git push -u origin master

38. Phase 1 最终验收

  • 五台虚拟机互通 OK
  • 五台 hostname OK
  • SSH 免密 OK
  • Ansible Inventory OK
  • ansible all -m ping OK
  • Ansible sudo OK
  • Linux 公共初始化 OK
  • /etc/hosts OK
  • chrony OK
  • K8s 三节点 Swap=0 OK

39. Phase 1 中最重要的经验

  1. 不要一开始就手工维护所有服务器:使用 Ansible 进行集中管理。
  2. 自动化必须考虑幂等与重复执行:配置文件应该做到执行一次正常,执行第二次也正常。
  3. 自动化修改网络必须灰度:采用先单节点验证,再批量执行,并使用 serial: 1
  4. 软件版本会影响自动化语法:注意 Ansible、Python 等软件的版本兼容性。
  5. Kubernetes 集群创建前必须固定网络基础:提前解决 hostname、IP、DNS、swap、内核参数等问题。

40. 下一阶段

Phase 1 已结束,接下来请参考 Phase 2: Docker + Kubernetes Container Runtime

计划新增:

ansible/
├── docker.yml
├── kubernetes.yml
└── scripts/
    ├── docker-install.sh
    ├── cri-dockerd-install.sh
    └── k8s-install.sh

下一步首先实现:

Ansible
    ↓
安装 Docker Engine
    ↓
配置 Docker daemon
    ↓
systemd cgroup
    ↓
启动 Docker
    ↓
验证 docker info
    ↓
安装 cri-dockerd
    ↓
启动 cri-docker.service
    ↓
验证 /run/cri-dockerd.sock
  • Docker 部署到:k8s-master, k8s-node1, k8s-node2, devops
  • cri-dockerd 只部署到:k8s-master, k8s-node1, k8s-node2

更多推荐