使用Terraform + Kubespray在KVM上构建Kubernetes集群

太长不读 bù dú)

在定义了组成之后,可以使用以下命令创建 Kubernetes 集群:

$ terraform init
$ terraform apply -auto-approve
$ docker run --rm -it \
  --mount type=bind,source="$(pwd)"/inventory,dst=/inventory \
  --mount type=bind,source="$(pwd)"/generate_inventory.py,dst=/kubespray/generate_inventory.py \
  --mount type=bind,source="$(pwd)"/terraform.tfstate,dst=/kubespray/terraform.tfstate \
  --mount type=bind,source="${HOME}"/.ssh/id_rsa,dst=/root/.ssh/id_rsa \
  quay.io/kubespray/kubespray:v2.23.1 bash

# Inside a container
$ ansible-playbook -i generate_inventory.py cluster.yml

样本代码:

 

简要概述

使用Terraform + Kubespray在KVM上构建Kubernetes集群。大致步骤如下。

    1. 使用 Terraform 创建虚拟机

 

    1. 从生成的 terraform.tfstate 文件中提取存货信息

 

    使用存货信息和 Kuberspray 的 Ansible playbooks 创建 Kubernetes 集群
dynamic_inventory.drawio.png

网络配置如下图所示:

network_architecture.drawio.png

在集群中,将公共网络(eth0)与用于Ansible SSH登录的网络(eth1)进行了分隔。

前提条件 (Qian ti tiao jian)

    • terraform

 

    • Container runtime (docker, podman, nerdctl, etc.)

 

    • KVM packages

qemu-kvm
libvirt

crdtools
nmcli
(Host OS: Ubuntu 22.04)

步骤

主持人的预先设置

网络设置

创建一个虚拟桥接口br0,并将现有主机的网络接口(如enp1s0)连接到br0。通过这样做,可以将后续创建的虚拟机连接到br0,使得可以直接从局域网中的任何主机访问虚拟机。

图示说明如下,将设置从 “Before” 更改为 “After”。

network-diff.drawio.png
$ HOST_IP=192.168.8.10
$ CIDR=24
$ GATEWAY=192.168.8.1
$ DNS=192.168.8.1
$ NWIF=enp1s0

# br0 を作成
$ nmcli connection add type bridge ifname br0
$ nmcli connection show
NAME                UUID                                  TYPE       DEVICE
bridge-br0          55bef68c-1232-46c3-adac-e40964c24d4d  bridge     br0
...

# 既存の enp1s0 と同じ設定を br0 に与える
$ nmcli connection modify bridge-br0 \
ipv4.method manual \
ipv4.addresses "$HOST_IP/$CIDR" \
ipv4.gateway "$GATEWAY" \
ipv4.dns $DNS

# enp1s0 を br0 に接続
$ nmcli connection add type bridge-slave ifname $NWIF master bridge-br0
# 既存の enp1s0 のネットワークインタフェースを削除
$ nmcli connection delete $NWIF

# br0 の設定を反映
$ nmcli connection up bridge-br0

检查libvirt的默认存储池。

首先,需要确认是否存在默认存储池,libvirt使用各种资源,这些资源在称为”default pool”的目录/var/lib/libvirt/images内进行管理。

$ virsh pool-list --all
 Name      State    Autostart
-------------------------------
 default   active   yes

如果没有默认池的话,在这里可以使用以下命令创建一个:

$ mkdir /var/lib/libvirt/images
$ chmod 755 /var/lib/libvirt/images

$ virsh pool-define /dev/stdin <<EOF
<pool type='dir'>
  <name>default</name>
  <target>
    <path>/var/lib/libvirt/images</path>
  </target>
</pool>
EOF

$ virsh pool-start default
$ virsh pool-autostart default
$ virsh pool-list --all

获取 Linux 镜像

在本文中,我们将使用Rocky Linux作为VM的操作系统,取代原先的CentOS。我们将从download.rockylinux.org网站下载映像文件,并将其保存到libvirt的默认存储池/var/lib/libvirt/images/中。

$ sudo curl -L -o /var/lib/libvirt/images/Rocky-9-GenericCloud.latest.x86_64.qcow2 https://download.rockylinux.org/pub/rocky/9.2/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2

使用 Terraform 进行虚拟机的配置

云初始化配置

cloud-init是一种用于自动化虚拟机的初始设置的机制,在许多Linux发行版中被采用。可以通过准备以下两个配置文件来提供设置。

cloud_init.cfg: ユーザ設定を記述

network_config.cfg: ネットワーク設定を記述

#cloud-config
users:
  - name: root
    ssh-authorized-keys:
      - "<YOUR_SSH_KEY>" # 自分の SSH 公開鍵を書く

runcmd:
  - dnf install -y python3
  - dnf install -y libselinux-python
当执行 kubespray 时,需要安装python3和libselinux-python。

网络设置由 network_config.cfg 提供。

然而,如果每个虚拟机有不同的值(例如分配静态 IP 地址),可以创建一个包含参数部分类似于 ${foo} 的占位符的模板,如下所示。使用 Terraform 的模板功能,稍后可以将值分配给它,并生成每个虚拟机的 network_config.cfg。

version: 2
ethernets:
  eth0:
    dhcp4: no
    addresses: [${ip}]
    gateway4: ${gateway}
    nameservers:
      addresses: ${nameservers}

准备 Terraform 的 libvirt 提供程序

使用Terraform libvirt provider (dmacvicar/libvirt),可以通过Terraform控制libvirt。

由于libvirt默认限制外部应用程序对libvirt资源的访问,因此为了给libvirt提供者授予访问权限,需要编辑/etc/apparmor.d/libvirt/TEMPLATE.qemu文件如下。

# This profile is for the domain whose UUID matches this file.
#

#include <tunables/global>

profile LIBVIRT_TEMPLATE flags=(attach_disconnected) {
  #include <abstractions/libvirt-qemu>
   /var/lib/libvirt/images/** rwk,
   /tmp/** rwk,
}

重新启动libvirt。

$ sudo systemctl restart libvirtd

准备Terraform文件

本节将解释如何实施用于构建Kubespray的VM的Terraform代码。这些代码被模块化了,所以如果你不知道如何实施,但是想要导入并使用它们,可以参考本文中关于将本文的Terraform代码作为模块使用的技巧。

文件由以下四部分组成:

    • provider.tf

 

    • main.tf

 

    • variables.tf

 

    output.tf

provider.tf (提供者.tf)

声明使用Terraform Libvirt Provider。

terraform {
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"
      version = "0.7.1"
    }
  }
}

provider "libvirt" {
  uri = var.libvirt_uri
}

主要.tf

主要描述与创建虚拟机相关的处理过程。

locals {
  cluster_cidr_splitted      = split("/", var.cidr)
  cluster_cidr_subnet        = local.cluster_cidr_splitted[0]
  cluster_cidr_prefix        = local.cluster_cidr_splitted[1]
  cluster_nameservers_string = "[\"${join("\", \"", var.nameservers)}\"]"

  # Auto-calculate mac address from IP 
  cluster_ips_parts = [for vm in var.vms : split(".", vm.public_ip)]
  cluster_mac_addrs = [
    for ip_parts in local.cluster_ips_parts : format(
      "52:54:00:%02X:%02X:%02X",
      tonumber(ip_parts[1]),
      tonumber(ip_parts[2]),
      tonumber(ip_parts[3])
    )
  ]
  private_ips_parts = [for vm in var.vms : split(".", vm.private_ip)]
  private_mac_addrs = [
    for ip_parts in local.private_ips_parts : format(
      "52:54:00:%02X:%02X:%02X",
      tonumber(ip_parts[1]),
      tonumber(ip_parts[2]),
      tonumber(ip_parts[3])
    )
  ]
}

data "template_file" "user_data" {
  count    = length(var.vms)
  template = file(var.vms[count.index].cloudinit_file)
}

data "template_file" "network_config" {
  count    = length(var.vms)
  template = file("${path.module}/network_config.cfg")
  vars = {
    ip          = var.vms[count.index].public_ip
    cidr_prefix = local.cluster_cidr_prefix
    gateway     = var.gateway
    nameservers = local.cluster_nameservers_string
  }
}

resource "libvirt_cloudinit_disk" "commoninit" {
  count          = length(var.vms)
  name           = "commoninit_${var.vms[count.index].name}.iso"
  user_data      = data.template_file.user_data[count.index].rendered
  network_config = data.template_file.network_config[count.index].rendered
}

locals {
  volume_list      = { for vm in var.vms : "${vm.name}" => flatten([for volume in vm.volumes : volume]) }
  volume_name_list = [for vm, volumes in local.volume_list : [for volume in volumes : { "name" : "${vm}_${volume.name}", "disk" : volume.disk }]]
  volumes          = flatten(local.volume_name_list)
  volumes_indexed  = { for index, volume in local.volumes : volume.name => index }
}

resource "libvirt_domain" "vm" {
  count  = length(var.vms)
  name   = var.vms[count.index].name
  vcpu   = var.vms[count.index].vcpu
  memory = var.vms[count.index].memory

  disk {
    volume_id = libvirt_volume.system[count.index].id
  }

  cloudinit = libvirt_cloudinit_disk.commoninit[count.index].id
  autostart = true

  # Public network
  network_interface {
    bridge    = var.bridge
    addresses = [var.vms[count.index].public_ip]
    mac       = local.cluster_mac_addrs[count.index]
  }

  # Private network
  network_interface {
    network_name = "default"
    addresses    = [var.vms[count.index].private_ip]
    mac          = local.private_mac_addrs[count.index]
  }

  qemu_agent = true

  cpu {
    mode = "host-passthrough"
  }

  graphics {
    type        = "vnc"
    listen_type = "address"
  }

  # Makes the tty0 available via `virsh console`
  console {
    type        = "pty"
    target_port = "0"
    target_type = "serial"
  }
}

resource "libvirt_volume" "system" {
  count          = length(var.vms)
  name           = "${var.vms[count.index].name}_system.qcow2"
  pool           = var.pool
  format         = "qcow2"
  base_volume_id = var.vm_base_image_uri
  size           = var.vms[count.index].disk
}

variables.tf的意思是tf是指Terraform,variables是指变量,.tf是指Terraform的文件扩展名。

声明变量。

variable "libvirt_uri" {
  type = string
}

variable "vm_base_image_uri" {
  type = string
}

variable "bridge" {
  type = string
}

variable "gateway" {
  type = string
}

variable "cidr" {
  type = string
}

variable "nameservers" {
  type = list(string)
}

variable "pool" {
  type    = string
  default = "default"
}

variable "vms" {
  type = list(
    object({
      name           = string
      vcpu           = number
      memory         = number
      disk           = number
      public_ip      = string
      private_ip     = string
      cloudinit_file = string

      kube_control_plane = bool
      kube_node          = bool
      etcd               = bool
    })
  )
}

output.tf 的汉语翻译是输出.tf。

在接下来的过程中,输出要传递给Ansible的主机信息。

locals {
  kubespray_hosts_keys = ["name", "kube_control_plane", "kube_node", "etcd"]
  kubespray_hosts = [for vm in var.vms :
    merge(
      {
        for key, value in vm : key => value if contains(local.kubespray_hosts_keys, key)
      },
      {
        ip        = vm.public_ip
        access_ip = vm.private_ip
    })
  ]
}

output "kubespray_hosts" {
  value = local.kubespray_hosts
}

执行 Terraform

执行Terraform并进行虚拟机配置。

$ terraform init
$ terraform apply -auto-approve

确认虚拟机已经被创建并正在运行。

$ virsh list --all
 Id   Name           State
------------------------------
 1    k8s-master-1   running
 2    k8s-worker-1   running
 3    k8s-worker-2   running

可以通过以下命令确认创建的虚拟机已连接到br0。

$ ip link show master br0
bridge name     bridge id               STP enabled     interfaces
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP mode DEFAULT group default qlen 1000
    link/ether 48:21:0b:57:b2:52 brd ff:ff:ff:ff:ff:ff
5: vnet4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether fe:54:00:00:00:04 brd ff:ff:ff:ff:ff:ff
6: vnet5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether fe:54:00:00:00:02 brd ff:ff:ff:ff:ff:ff
7: vnet6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether fe:54:00:00:00:03 brd ff:ff:ff:ff:ff:ff
8: vnet7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether fe:54:00:00:00:01 brd ff:ff:ff:ff:ff:ff

使用 Kubespray 来创建 Kubernetes 集群。

Kubespray是一个开源项目,提供了自动化构建Kubernetes集群的Ansible playbook。按照kubespray/docs/setting-up-your-first-cluster.md at master · kubernetes-sigs/kubespray中的步骤进行操作。

首先,克隆 kubespray 的代码库。

$ git clone git@github.com:kubernetes-sigs/kubespray.git
$ cd kubespray

接下来,复制现有的样本并创建配置文件的基础。

$ git checkout release-2.23
$ cp -rfp inventory/sample inventory/mycluster

在本文中,我们决定使用crio作为容器运行时(默认情况下是containerd)。将group_vars/k8s_cluster/k8s_cluster.yml中的container_manager的值更改为crio。

## Container runtime
## docker for docker, crio for cri-o and containerd for containerd.
## Default: containerd
container_manager: crio

如果要设置更详细的选项,请修改 inventory/mycluster/group_vars/ 目录下的文件(附注:代理设置)。

创建库存清单

我将介绍以下两种方法,但你只需要选择其中一种就可以了。

    1. 使用动态库存的方法。

 

    手动编辑hosts.yaml文件的方法。
使用动态库存方法

在执行terraform后,从生成的terraform.tfstate文件中动态地提取清单信息。

只需准备一个仅进行输出的脚本(示例实现:./generate_inventory.py),该脚本将输出以 JSON 描述的 inventory 信息。以下是一个读取 terraform.state 的 .outputs 并创建 inventory 的脚本实现示例:

#!/usr/bin/env python3

import json
import re


def main():
    output = get_outputs()
    hosts = output['kubespray_hosts']['value']
    libvirt_uri = output['libvirt_uri']['value']

    hostvars = {}
    kube_control_plane = []
    kube_node = []
    etcd = []

    for host in hosts:
        name = host['name']
        ip = host['ip']
        access_ip = host['access_ip']
        hostvars.update({
          name: {
              "ansible_host": access_ip,
              "ip": ip,
          }
        })

        regex = r"^qemu(\+ssh)?://([^/]*)/.*"
        res = re.match(regex, libvirt_uri)
        if res:
          hostname = res[2]
          if hostname != "":
            hostvars[name].update({
              "ansible_ssh_common_args": f"-J {hostname}"
          })

        if host["kube_control_plane"]:
            kube_control_plane.append(name)
        if host["kube_node"]:
            kube_node.append(name)
        if host["etcd"]:
            etcd.append(name)

    inventory = {
        "_meta": {
            "hostvars": hostvars,
        },
        "kube_control_plane": kube_control_plane,
        "kube_node": kube_node,
        "etcd": etcd,
        "k8s_cluster": {
            "children": [
                "kube_control_plane",
                "kube_node",
            ]
        }
    }


    print(json.dumps(inventory))


def get_outputs():
    tfstate_path = './terraform.tfstate'
    with open(tfstate_path) as f:
        tfstate = json.load(f)
    return tfstate['outputs']


main()

运行此generate_inventory.py脚本会输出以下JSON:

{
  "_meta": {
    "hostvars": {
      "storage1": {
        "ansible_host": "192.168.122.201",
        "ip": "192.168.8.201"
      },
      "storage2": {
        "ansible_host": "192.168.122.202",
        "ip": "192.168.8.202"
      },
      "storage3": {
        "ansible_host": "192.168.122.203",
        "ip": "192.168.8.203"
      }
    }
  },
  "kube_control_plane": [
    "storage1"
  ],
  "kube_node": [
    "storage1",
    "storage2",
    "storage3"
  ],
  "etcd": [
    "storage1"
  ],
  "k8s_cluster": {
    "children": [
      "kube_control_plane",
      "kube_node"
    ]
  }
}

指定这个作为库存,并创建Kubernetes集群:

$ docker pull quay.io/kubespray/kubespray:v2.23.1
$ docker run --rm -it \
  --mount type=bind,source="$(pwd)"/inventory,dst=/inventory \
  --mount type=bind,source="$(pwd)"/generate_inventory.py,dst=/kubespray/generate_inventory.py \
  --mount type=bind,source="$(pwd)"/terraform.tfstate,dst=/kubespray/terraform.tfstate \
  --mount type=bind,source="${HOME}"/.ssh/id_rsa,dst=/root/.ssh/id_rsa \
  quay.io/kubespray/kubespray:v2.23.1 bash

# Inside a container
$ ansible-playbook -i generate_inventory.py cluster.yml
…
PLAY RECAP *****************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
k8s-master-1               : ok=765  changed=138  unreachable=0    failed=0    skipped=1263 rescued=0    ignored=8   
k8s-worker-1               : ok=583  changed=104  unreachable=0    failed=0    skipped=795  rescued=0    ignored=2   
k8s-worker-2               : ok=523  changed=82   unreachable=0    failed=0    skipped=763  rescued=0    ignored=1   

Tuesday 01 August 2023  13:16:52 +0000 (0:00:00.041)       0:59:01.983 ********                                                                                      ===============================================================================      

如果在安装过程中出现故障或其他原因想要重新安装的话,可以通过以下方法进行重置。

$ ansible-playbook -i ./generate_inventory.py reset.yml

请参考Kubespray官方仓库的 https://github.com/kubernetes-sigs/kubespray/blob/master/docs/aws.md#dynamic-inventory ,获取有关Kubespray动态清单的详细信息。

(选项2) 手动编辑 hosts.yaml

启动 kubespray 容器。

$ docker pull quay.io/kubespray/kubespray:v2.23.1
$ docker run --rm -it \
  --mount type=bind,source="$(pwd)"/inventory/mycluster,dst=/inventory \
  --mount type=bind,source="${HOME}"/.ssh/id_rsa,dst=/root/.ssh/id_rsa \
  quay.io/kubespray/kubespray:v2.23.1 bash

进入容器后,执行以下命令。

$ declare -a IPS=(192.168.8.201 192.168.8.202 192.168.8.203)
$ CONFIG_FILE=/inventory/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]}

因为生成了存货文件 /inventory/mycluster/hosts.yaml,因此根据需要进行修正主机信息。在本文中,我们使用以下配置:

all:
  hosts:
    storage1:
      ansible_host: 192.168.122.201
      ip: 192.168.8.201
    storage2:
      ansible_host: 192.168.122.202
      ip: 192.168.8.202
    storage3:
      ansible_host: 192.168.122.203
      ip: 192.168.8.203
  children:
    kube_control_plane:
      hosts:
        storage1:
    kube_node:
      hosts:
        storage1:
        storage2:
        storage3:
    etcd:
      hosts:
        storage1:
    k8s_cluster:
      children:
        kube_control_plane:
        kube_node:
$ docker run --rm -it \
  --mount type=bind,source="$(pwd)"/inventory,dst=/inventory \
  --mount type=bind,source="$(pwd)"/cluster.yml,dst=/kubespray/cluster.yml \
  --mount type=bind,source="${HOME}"/.kube,dst=/root/.kube \
  --mount type=bind,source="${HOME}"/.ssh/id_rsa,dst=/root/.ssh/id_rsa \
  quay.io/kubespray/kubespray:v2.23.1 bash

# Inside a container
$ ansible-playbook -i /inventory/hosts.yaml cluster.yml

访问 Kubernetes 集群

从任一主节点获取认证信息 admin.conf。

$ mkdir -p ~/.kube
$ scp root@192.168.8.201:/etc/kubernetes/admin.conf ~/.kube

请将 admin.conf 文件中 clusters.cluster.server 的 IP 地址修改为公共IP地址(例如:192.168.8.201),原地址为127.0.0.1。

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: <CERTIFICATE_AUTHORITY_DATA>
    server: https://192.168.8.201:6443
  name: cluster.local
...

在.bashrc等文件中设置以下环境变量。

$ export KUBECONFIG=$HOME/.kube/admin.conf 

通过以上的步骤,您可以使用 kubectl 进入集群。

$ kubectl cluster-info
Kubernetes control plane is running at https://192.168.8.201:6443

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

$ kubectl get nodes
NAME           STATUS   ROLES           AGE   VERSION
k8s-master-1   Ready    control-plane   70m   v1.26.5
k8s-worker-1   Ready    <none>          68m   v1.26.5
k8s-worker-2   Ready    <none>          68m   v1.26.5

技巧

使用Kubespray进行代理配置

如果要设置代理,请编辑inventory/mycluster/group_vars/all/all.yml文件中的以下项目。

http_proxy: 'http://your-proxy-server:8080'
https_proxy: 'http://your-proxy-server:8080'
no_proxy: 'localhost,127.0.0.1,.yourdomain.com'

将自己的playbook集成到Kubespray中。

如果在应用cluster.yml之后需要执行自定义的附加处理,可以这样操作。

查看Kubespray的cluster.yml文件,可以看出只是简单地导入了playbooks/cluster.yml文件,所以如果需要执行额外的任务,需要在cluster.yml的末尾添加定制化的处理,创建一个名为customized_cluster.yml的文件。

---
# This role assumes to call Kubespray's `playbooks/cluster`.
- name: Install Kubernetes
  ansible.builtin.import_playbook: playbooks/cluster.yml

- name: Registernqualified
  hosts: all
  tasks:
    - ansible.builtin.file:
        path: /etc/containers/registries.conf.d
        state: directory
        mode: '0755'
    - ansible.builtin.copy:
        dest: /etc/containers/registries.conf.d/01-unqualified.conf
        content: |

          unqualified-search-registries = ['docker.io', 'quay.io']

- name: Download amind.conf to localhost
  hosts: kube_control_plane
  run_once: true
  tasks:
    - ansible.builtin.fetch:
        src: /etc/kubernetes/admin.conf
        dest: ~/.kube/admin.conf
        flat: yes
    - delegate_to: localhost
      ansible.builtin.replace:
        path: ~/.kube/admin.conf
        regexp: '127.0.0.1'
        replace: "{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}"

在上面的例子中,下一步的任务是创建集群。

    • Unqualified registries (docker.io と quay.io) の追加

 

    admin.conf をローカルにダウンロードし、API サーバのアドレスを Public IP に置換

将其自动化。只需将Kubespray的cluster.yml替换为此文件,然后按照正常 playbook 的方式执行即可。

$ docker run --rm -it \
  --mount type=bind,source="$(pwd)"/inventory,dst=/inventory \
  --mount type=bind,source="$(pwd)"/.terraform/modules/kubernetes/kubernetes/generate_inventory.py,dst=/kubespray/generate_inventory.py \
  --mount type=bind,source="$(pwd)"/terraform.tfstate,dst=/kubespray/terraform.tfstate \
  --mount type=bind,source="$(pwd)"/cluster.yml,dst=/kubespray/cluster.yml \
  --mount type=bind,source="${HOME}"/.kube,dst=/root/.kube \
  --mount type=bind,source="${HOME}"/.ssh/id_rsa,dst=/root/.ssh/id_rsa \
  quay.io/kubespray/kubespray:v2.23.1 bash

# Inside a container
ansible-playbook -i generate_inventory.py cluster.yml

请参考 kubespray/docs/integration.md at master · kubernetes-sigs/kubespray · GitHub 中的公式指南。

在离线环境下执行 Terraform

通常情况下,Provider 是通过互联网自动下载的,但在离线环境下,您可以在主目录下准备一个 ~/terraform.d/providers 文件夹,并将 Provider 放在那里。

以Terraform Libvirt Provider为例,从dmacvicar/terraform-provider-libvirt的Releases页面下载二进制文件,并按照以下方式进行配置:

~/terraform.d/providers/
└── providers/
    └── registry.terraform.io/
        └── dmacvicar/
            └── libvirt/
                └── 0.7.1/
                    └── linux_amd64/
                        ├── CHANGELOG.md
                        ├── LICENSE
                        ├── README.md
                        └── terraform-provider-libvirt_v0.7.4

在遠程主機上應用 Terraform.

可以从本地执行Terraform,并在远程主机上创建虚拟机。

首先,在远程主机上完成主机的预配置后,确认libvirtd正在运行。

# Remote side
$ systemctl status libvirtd

通过使用 qemu+ssh://协议,从客户端验证连接:

# Client side
$ virsh -c qemu+ssh://<user>@<remote_ip>/system list
 Id   Name   State
--------------------

确认后,在 main.tf 文件中指定以下的远程主机信息,并在客户端上执行 terraform apply。

locals {
  user_home_directory = pathexpand("~")
}

provider "libvirt" {
  uri = "qemu+ssh://<remote-user>@<remote_host>/system?keyfile=${local.user_home_directory}/.ssh/id_rsa&known_hosts_verify=ignore"
}

如果通过跳板服务器的话

当通过跳板服务器远程访问时,需要将远程的22端口转发到本地的一个适当端口(例如:50000)。

# Client side
$ ssh -C -N -f -L 50000:<remote-user>@<remote-host>:22 <bastion-host> -p <bastion-port>
$ virsh -c qemu+ssh://<remote-user>@localhost:50000/system list
 Id   Name   State
--------------------

locals {
  user_home_directory = pathexpand("~")
}

provider "libvirt" {
  uri = "qemu+ssh://<remote-user>@localhost:50000/system?keyfile=${local.user_home_directory}/.ssh/id_rsa&known_hosts_verify=ignore"
}

将这篇文章中的 Terraform 代码作为模块使用

准备一个如下的 main.tf 文件。在这个例子中,将创建一个由1个主节点和2个工作节点组成的共计3台虚拟机(VM),但请根据所需的配置适当修改参数。

output "kubespray_hosts" {
  value = module.kubernetes.kubespray_hosts
}

output "libvirt_uri" {
  value = module.kubernetes.libvirt_uri
}

locals {
  user_home_directory = pathexpand("~")
}

module "kubernetes" {
  source = "github.com/sawa2d2/k8s-on-kvm//kubernetes/"

  ## Localhost:
  # libvirt_uri = "qemu:///system"
  ## Remote:
  # libvirt_uri = "qemu+ssh://<user>@<remote-host>/system?keyfile=${local.user_home_directory}/.ssh/id_rsa&known_hosts_verify=ignore"
  ## Remote via bastion:
  ##   Forward port in advance.
  ##   $ ssh -C -N -f -L 50000:<remote-user>@<remote-host>:22 <bastion-host> -p <bastion-port>
  # libvirt_uri = "qemu+ssh://<remote-user>@localhost:50000/system?keyfile=${local.user_home_directory}/.ssh/id_rsa&known_hosts_verify=ignore"
  libvirt_uri = "qemu:///system"

  # Download the image by:
  #   sudo curl -L -o /var/lib/libvirt/images/Rocky-9-GenericCloud.latest.x86_64.qcow2 https://download.rockylinux.org/pub/rocky/9.2/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2 
  vm_base_image_uri = "/var/lib/libvirt/images/Rocky-9-GenericCloud.latest.x86_64.qcow2"
  pool              = "default"

  # Cluster network
  bridge      = "br0"
  cidr        = "192.168.8.0/24"
  gateway     = "192.168.8.1"
  nameservers = ["192.168.8.1"]

  vms = [
    {
      name           = "k8s-master-1"
      vcpu           = 4
      memory         = 16000                    # in MiB
      disk           = 100 * 1024 * 1024 * 1024 # 100 GB
      public_ip      = "192.168.8.101"
      private_ip     = "192.168.122.201"
      cloudinit_file = "cloud_init.cfg"
      volumes        = []

      kube_control_plane = true
      kube_node          = true
      etcd               = true
    },
    {
      name           = "k8s-worker-1"
      vcpu           = 4
      memory         = 16000                    # in MiB
      disk           = 100 * 1024 * 1024 * 1024 # 100 GB
      public_ip      = "192.168.8.102"
      private_ip     = "192.168.122.202"
      cloudinit_file = "cloud_init.cfg"
      volumes        = []

      kube_control_plane = false
      kube_node          = true
      etcd               = false
    },
    {
      name           = "k8s-worker-2"
      vcpu           = 2
      memory         = 8000                     # in MiB
      disk           = 100 * 1024 * 1024 * 1024 # 100 GB
      public_ip      = "192.168.8.103"
      private_ip     = "192.168.122.203"
      cloudinit_file = "cloud_init.cfg"
      volumes        = []

      kube_control_plane = false
      kube_node          = true
      etcd               = false
    },
  ]
}

以下是参考资料

Terraform 和 Dynamic Inventory 相关

    • End-to-End Application Provisioning with Ansible and Terraform – IBM Blog

 

    Integrating Ansible and Jenkins with Terraform to make a powerful infrastructure | by Ankush Chavan | Medium

libvirt的配置相关

error when default storage pool is missing · Issue #8 · simon3z/virt-deploy … default pool の作成方法など

关于Kuberspray的相关内容

    • kubesprayを使ったKubernetesのインストール | Kubernetes

 

    An introduction to Kubespray | Enable Sysadmin

桥梁设置相关

    KVMでゲストOSをブリッジ接続する – Qiita

相关文章

 

广告
将在 10 秒后关闭
bannerAds