深入浅出边缘云 | 3. 资源配置(下)

简介: 深入浅出边缘云 | 3. 资源配置(下)
3.1.3 Provisioning API


到目前为止,根据我们所介绍的步骤,可以假设每个服务器和交换机都已启动并运行,但是仍然需要做一些工作为配置栈中的下一层准备裸金属集群,本质上是在图 11 所示的混合云的左右两边之间建立对应关系。如果你问自己"谷歌会怎么做?"这就减少了为裸金属边缘云设置类似 GCP API 的任务。这个 API 主要包含了 Kubernetes API,但不仅提供了使用 Kubernetes 的方法,还包括了管理 Kubernetes 的调用。


简而言之,这个"管理 Kubernetes"的任务就是把一组相互连接的服务器和交换机变成一个完全实例化的 Kubernetes 集群。对于初学者来说,API 需要提供一种在每个物理集群上安装和配置 Kubernetes 的方法,包括指定运行哪个版本的 Kubernetes,选择正确的容器网络接口(CNI)插件(虚拟网络适配器)组合,以及将 Kubernetes 连接到本地网络(以及可能需要的任何 VPN)。这一层还需要提供一种方法来设置访问和使用每个 Kubernetes 集群的帐户(和相关凭据),以及管理部署在给定集群上的独立项目的方法(例如为多个应用程序管理名称空间)。


例如,Aether 目前使用 Rancher 管理裸金属集群上的 Kubernetes, Rancher 的一个集中化实例负责管理所有边缘站点。这将产生如图 16 所示的配置,为了强调 Rancher 的范围,显示了多个边缘集群。虽然图中没有显示,但 GCP 提供的 API,就像 Rancher 一样,也跨越了多个物理站点(例如,us-west1-aeurope-north1-basia-south2-c,等等)。


image.png

图 16. 混合云中的配置,包括一个用于管理运行在多个裸金属集群上的 Kubernetes 的 API 层。


最后需要指出的是,虽然我们经常把 Kubernetes 当作全行业标准来对待,但事实并非如此,每个云供应商都提供自己的定制版本:


  • Microsoft Azure 提供了 Azure Kubernetes Service (AKS)
  • AWS 提供了 Amazon Elastic Kubernetes Service (EKS)
  • Google Cloud 提供了 Google Kubernetes Engine (GKE)
  • Aether 边缘云运行 Rancher 认证的 Kubernetes 版本 (RKE)


虽然 CNCF(云原生计算基金会,负责管理 Kubernetes 项目的开源组织)对这些版本和其他版本的 Kubernetes 进行了认证,但这只是建立了一致性基线。每个版本都可以在此基础上进行改进,这些改进通常以附加特性的形式提供并控制 Kubernetes 集群。我们在云管理层的工作是为运营商提供一种管理这种异构性的方法。正如我们将在第 3.2 节中看到的,这是基础设施即代码层解决的主要挑战。


3.1.4 配置虚拟机


通过考虑提供虚拟机(VM)的含义,我们结束了对配置物理机所需步骤的讨论。当我们向 AKS、EKS 或 GKE 请求 Kubernetes 集群时,这是"幕后"发生的事情,因为超大规模云服务商可以选择将 Kubernetes 服务分层放在他们的基础设施即服务(IaaS)之上。我们正在构建的边缘云也需要类似的东西吗?


不一定。因为我们的目标是支持一组精心策划的边缘服务,为企业用户提供价值,而不是支持不受信任的第三方启动他们想要的任何应用程序的容器即服务(Container-as-a-Service),所以不需要"作为服务"来管理 VM。但是,我们仍然可能希望使用 VM 作为一种将 Kubernetes 的工作负载隔离在有限数量的物理服务器上的方法。这可以作为配置的一个步骤,类似于连接和引导物理机,但使用 KVM 和 Proxmox 等虚拟化机制来完成,而不需要类似 OpenStack 这样成熟的 IaaS 机制。然后,这些 VM 将被记录为 NetBox 和本节介绍的其他工具中的一级云资源,与物理机器没有区别。


考虑到 Kubernetes 允许我们在单个集群上部署多个应用程序,为什么要这样做呢?这个问题没有固定答案。一个原因是支持细粒度的资源隔离,从而可以(a)确保每个 Kubernetes 应用能够获取完成工作所需的处理器、内存和存储资源,(b)减少应用程序之间的信息泄露风险。例如,假设除了 SD-Fabric、SD-RAN 和 SD-Core 工作负载(默认情况下)运行在每个边缘站点之外,我们还想运行一个或多个其他边缘应用,比如在 2.3 节中介绍的 OpenVINO 平台。为了确保这些应用程序之间不存在干扰,可以为每个应用专门部署一个物理服务器子集。物理分区是共享物理集群的粗粒度方法。通过实例化 VM,能够在多个应用之间"分割"一个或多个服务器,这为运维人员分配资源提供了更大的灵活性,通常意味着更少的总体资源需求。请注意,还有其他方法可以指定如何在应用程序之间共享集群资源(我们将在第 4.4 节中看到),但是配置(provisioning)层是可以解决这个问题的一个选项。


3.2 基础设施即代码(Infrastructure-as-Code)


刚刚介绍的 Kubernetes 配置接口包含可编程 API、命令行接口(CLI)和图形用户界面(GUI)。如果你尝试了本书推荐的任何教程,可能会使用后两种教程中的一种。然而,对于运维部署来说,让运维人员与 CLI 或 GUI 交互是有问题的,不仅因为人类容易出错,还因为几乎不可能始终如一地重复一系列配置步骤。能够持续重复这个过程是下一章所介绍的生命周期管理的核心。


解决方案是以声明式语法定义基础架构,包含需要实例化的 Kubernetes 集群信息(例如,一部分运行在裸金属上的边缘集群,一部分在 GCP 中实例化),以及相关配置信息,然后自动化调用可编程 API。这是"基础设施即代码"的本质,正如前面说的,我们使用 Terraform 作为开源示例。


由于 Terraform 规范是声明式的,所以理解的最佳方法是浏览特定示例。这样做的目的不是记录 Terraform(对更详细的内容感兴趣的人可以使用在线文档和循序渐进的教程),而是建立关于该层在管理云方面所扮演角色的直觉。


延伸阅读:

Terraform Documentation.

Terraform Getting Started Tutorials.


为了理解示例,关于 Terraform 配置语言,其主要内容在于提供了一种方法(1)为不同类型的资源指定模板(这些是.tf文件),(2)为这些资源模板的特定实例填充变量(这些是.tfvars文件)。然后给定一组.tf.tfvars文件,Terraform 实现两阶段过程。第一阶段,基于执行的前一个计划以来发生的变化构建执行计划。第二阶段,Terraform 执行一系列任务,使底层基础设施符合最新定义的规格说明。请注意,目前我们的工作是编写这些规格文件,并将它们签入配置存储库(Config Repo)。在第 4 章中,Terraform 将作为 CI/CD 流水线的一部分被调用。


现在来看具体的文件。在最上层,运维人员定义了计划合并到基础设施中的供应商(provider) 集合。我们可以认为每个供应商对应于一个云后端,提供了图 16 中介绍的相应配置 API。在我们的示例中,只展示两个供应商: Rancher 管理的边缘集群和 GCP 管理的集中式集群。注意,示例文件为每个供应商声明了一组相关变量(例如urlaccess-key),这些变量由下面介绍的特定实例的变量文件"填充"。


terraform {
  required_version = ">= 0.13"
  required_providers {
    rancher2 = {
      source  = "rancher/rancher2"
      version = "= 1.15.1"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 3.65.0"
    }
    null = {
      source  = "hashicorp/null"
      version = "~> 2.1.2"
    }
  }
}
variable "rancher" {
  description = "Rancher credential"
  type = object({
    url        = string
    access_key = string
    secret_key = string
  })
}
variable "gcp_config" {
  description = "GCP project and network configuration"
  type = object({
    region          = string
    compute_project = string
    network_project = string
    network_name    = string
    subnet_name     = string
  })
}
provider "rancher2" {
  api_url    = var.rancher.url
  access_key = var.rancher.access_key
  secret_key = var.rancher.secret_key
}
provider "google" {
  # Provide GCP credential using GOOGLE_CREDENTIALS environment variable
  project = var.gcp_config.compute_project
  region  = var.gcp_config.region
}


下一步是为我们希望配置的实际集群集填充详细信息(定义值)。让我们来看两个示例,对应于刚才指定的两个供应商。第一个显示了由 GCP 托管的集群(名为amp-gcp),托管 AMP 工作负载。(类似的有一个sdcore-gcp托管 SD-Core 实例。)Terraform 通过给特定集群分配相关标签(例如,env = "production")和管理堆栈的其他层(根据相关标签有选择的采取不同的操作)之间建立联系,我们将在第 4.4 节中看到使用这些标签的示例。


cluster_name = "amp-gcp"
cluster_nodes = {
  amp-us-west2-a = {
    host        = "10.168.0.18"
    roles       = ["etcd", "controlplane", "worker"]
    labels      = []
    taints      = []
  },
  amp-us-west2-b = {
    host        = "10.168.0.17"
    roles       = ["etcd", "controlplane", "worker"]
    labels      = []
    taints      = []
  },
  amp-us-west2-c = {
    host        = "10.168.0.250"
    roles       = ["etcd", "controlplane", "worker"]
    labels      = []
    taints      = []
  }
}
cluster_labels = {
  env          = "production"
  clusterInfra = "gcp"
  clusterRole  = "amp"
  k8s          = "self-managed"
  backup       = "enabled"
}


第二个示例展示了一个在 Site X 上实例化的边缘集群(名为ace-X)。在示例代码中可以看到,这是一个由 5 个服务器和 4 个交换机(两个叶交换机和两个脊交换机)组成的裸金属集群。每个设备的地址必须与 3.1 节中介绍的硬件配置阶段分配的地址相匹配。理想情况下,该节中介绍的 NetBox(以及相关的)工具链将自动生成 Terraform 变量文件,但在实践中,通常仍然需要手动输入数据。


cluster_name  = "ace-X"
cluster_nodes = {
  leaf1 = {
    user        = "terraform"
    private_key = "~/.ssh/id_rsa_terraform"
    host        = "10.64.10.133"
    roles       = ["worker"]
    labels      = ["node-role.aetherproject.org=switch"]
    taints      = ["node-role.aetherproject.org=switch:NoSchedule"]
  },
  leaf2 = {
    user        = "terraform"
    private_key = "~/.ssh/id_rsa_terraform"
    host        = "10.64.10.137"
    roles       = ["worker"]
    labels      = ["node-role.aetherproject.org=switch"]
    taints      = ["node-role.aetherproject.org=switch:NoSchedule"]
  },
  spine1 = {
    user        = "terraform"
    private_key = "~/.ssh/id_rsa_terraform"
    host        = "10.64.10.131"
    roles       = ["worker"]
    labels      = ["node-role.aetherproject.org=switch"]
    taints      = ["node-role.aetherproject.org=switch:NoSchedule"]
  },
  spine2 = {
    user        = "terraform"
    private_key = "~/.ssh/id_rsa_terraform"
    host        = "10.64.10.135"
    roles       = ["worker"]
    labels      = ["node-role.aetherproject.org=switch"]
    taints      = ["node-role.aetherproject.org=switch:NoSchedule"]
  },
  server-1 = {
    user        = "terraform"
    private_key = "~/.ssh/id_rsa_terraform"
    host        = "10.64.10.138"
    roles       = ["etcd", "controlplane", "worker"]
    labels      = []
    taints      = []
  },
  server-2 = {
    user        = "terraform"
    private_key = "~/.ssh/id_rsa_terraform"
    host        = "10.64.10.139"
    roles       = ["etcd", "controlplane", "worker"]
    labels      = []
    taints      = []
  },
  server-3 = {
    user        = "terraform"
    private_key = "~/.ssh/id_rsa_terraform"
    host        = "10.64.10.140"
    roles       = ["etcd", "controlplane", "worker"]
    labels      = []
    taints      = []
  },
  server-4 = {
    user        = "terraform"
    private_key = "~/.ssh/id_rsa_terraform"
    host        = "10.64.10.141"
    roles       = ["worker"]
    labels      = []
    taints      = []
  },
  server-5 = {
    user        = "terraform"
    private_key = "~/.ssh/id_rsa_terraform"
    host        = "10.64.10.142"
    roles       = ["worker"]
    labels      = []
    taints      = []
  }
}
cluster_labels = {
  env          = "production"
  clusterInfra = "bare-metal"
  clusterRole  = "ace"
  k8s          = "self-managed"
  coreType     = "4g"
  upfType      = "up4"
}


最后一块拼图是填写关于如何实例化每个 Kubernetes 集群的其余细节。本例中,我们只展示用于配置边缘集群的特定于 RKE 模块,如果你理解 Kubernetes,那么其中大部分细节都很简单。例如,该模块指定每个边缘集群应该加载calicomultus CNI 插件,还定义了如何调用kubectl根据这些规范配置 Kubernetes。也许对 SCTPSupport 的引用会比较陌生,这表明特定的 Kubernetes 集群是否需要支持 SCTP, SCTP 是一种面向电信的网络协议,并不包含在普通 Kubernetes 部署中,但 SD-Core 需要。


terraform {
  required_providers {
    rancher2 = {
      source  = "rancher/rancher2"
    }
    null = {
      source  = "hashicorp/null"
      version = "~> 2.1.2"
    }
  }
}
resource "rancher2_cluster" "cluster" {
  name = var.cluster_config.cluster_name
  enable_cluster_monitoring = false
  enable_cluster_alerting   = false
  labels = var.cluster_labels
  rke_config {
    kubernetes_version = var.cluster_config.k8s_version
    authentication {
      strategy = "x509"
    }
    monitoring {
      provider = "none"
    }
    network {
      plugin = "calico"
    }
    services {
      etcd {
        backup_config {
          enabled        = true
          interval_hours = 6
          retention      = 30
        }
        retention = "72h"
        snapshot  = false
      }
      kube_api {
        service_cluster_ip_range = var.cluster_config.k8s_cluster_ip_range
        extra_args = {
          feature-gates = "SCTPSupport=True"
        }
      }
      kubelet {
        cluster_domain     = var.cluster_config.cluster_domain
        cluster_dns_server = var.cluster_config.kube_dns_cluster_ip
        fail_swap_on       = false
        extra_args = {
          cpu-manager-policy = "static"
          kube-reserved      = "cpu=500m,memory=256Mi"
          system-reserved    = "cpu=500m,memory=256Mi"
          feature-gates      = "SCTPSupport=True"
        }
      }
      kube_controller {
        cluster_cidr             = var.cluster_config.k8s_pod_range
        service_cluster_ip_range = var.cluster_config.k8s_cluster_ip_range
        extra_args = {
          feature-gates = "SCTPSupport=True"
        }
      }
      scheduler {
        extra_args = {
          feature-gates = "SCTPSupport=True"
        }
      }
      kubeproxy {
        extra_args = {
          feature-gates = "SCTPSupport=True"
          proxy-mode    = "ipvs"
        }
      }
    }
    addons_include = ["https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/release-3.7/images/multus-daemonset.yml"]
    addons = var.addon_manifests
  }
}
resource "null_resource" "nodes" {
  triggers = {
    cluster_nodes = length(var.nodes)
  }
  for_each = var.nodes
  connection {
    type                = "ssh"
    bastion_host        = var.bastion_host
    bastion_private_key = file(var.bastion_private_key)
    bastion_user        = var.bastion_user
    user        = each.value.user
    host        = each.value.host
    private_key = file(each.value.private_key)
  }
  provisioner "remote-exec" {
    inline = [<<EOT
      ${rancher2_cluster.cluster.cluster_registration_token[0].node_command} \
      ${join(" ", formatlist("--%s", each.value.roles))} \
      ${join(" ", formatlist("--taints %s", each.value.taints))} \
      ${join(" ", formatlist("--label %s", each.value.labels))}
      EOT
    ]
  }
}
resource "rancher2_cluster_sync" "cluster-wait" {
  cluster_id = rancher2_cluster.cluster.id
  provisioner "local-exec" {
    command = <<EOT
      kubectl set env daemonset/calico-node \
        --server ${yamldecode(rancher2_cluster.cluster.kube_config).clusters[0].cluster.server} \
        --token ${yamldecode(rancher2_cluster.cluster.kube_config).users[0].user.token} \
        --namespace kube-system \
        IP_AUTODETECTION_METHOD=${var.cluster_config.calico_ip_detect_method}
    EOT
  }
}


还有其他一些松耦合端点需要绑定,例如定义用于连接边缘集群到 GCP 中对应节点的 VPN,但是上面示例足以说明基础设施即代码在云管理堆栈中所扮演的角色。关键是 Terraform 处理的所有事情都可以由人工运维人员在后端配置 API 上通过一系列 CLI 命令(或 GUI 点击)来完成,但经验表明,这种方法容易出错,而且难以重复。从声明式语言开始并自动生成正确的 API 调用序列是克服这个问题的一种经过验证的方法。


最后请注意这样一个事实: 虽然我们现在为云基础设施定义了一个声明性规范,我们称之为 Aether 平台,但这些规范文件是我们在配置存储库中签入的一个软件工件。这就是我们所说的"基础架构即代码": 基础架构规范被签入到存储库中,并像任何其他代码一样接受版本控制。这个存储库反过来为下一章介绍的生命周期管理流水线提供了输入。3.1 节中介绍的物理配置步骤发生在流水线的"外部"(这就是为什么我们不只是将资源配置加入生命周期管理),但将资源配置看作生命周期管理的"阶段 0"是比较公平的定义。


3.3 平台定义


定义系统架构(在我们的例子中是混合云的管理框架)的艺术在于决定在平台中包含什么以及在平台上运行的应用程序之间划清界限。对于 Aether,我们决定在平台中包含 SD-Fabric(以及 Kubernetes),而 SD-Core 和 SD-RAN 被视为应用程序,尽管这三者都是作为基于 Kubernetes 的微服务实现的。这个决定的后果是 SD-Fabric 被初始化为本章介绍的配置系统的一部分(与 NetBox、Ansible、Rancher 和 Terraform 扮演角色共同完成),而 SD-Core 和 SD-RAN 是基于第 4 章介绍的应用级机制部署。


可能还有其他边缘应用作为 Kubernetes 工作负载运行,这使情况变得更加复杂,因为从他们的角度来看,所有 Aether 组件(包括 SD-Core 和 SD-RAN 实现的 5G 连接)都假定是平台的一部分。换句话说,Aether 划了两条线,一条划分了 Aether 基础平台(Kubernetes 加上 SD-Fabric),另一条划分了 Aether PaaS(包括运行在平台上的 SD-Core 和 SD-RAN,加上管理整个系统的 AMP)。"基础平台"和"PaaS"之间的区别很细微,但本质上分别对应于软件堆栈和托管服务。


从某些方面来说,这只是一个术语问题,当然也很重要,不过与我们的讨论相关的是,由于有多个重叠机制,因此我们有不止一种方法来解决遇到的每个工程问题,从而很容易对可分离关注点实现不必要的合并而结束。明确、一致的界定什么是平台、什么是应用是健全的整体设计的先决条件。同样重要的是要认识到内部工程决策(例如,使用什么机制来部署给定组件)和外部可见的体系架构决策(例如,通过公共 API 公开什么功能)之间的区别。

目录
相关文章
|
8月前
|
存储 边缘计算 数据处理
边缘云概述
边缘云是分布式云数据中心,位于网络边缘,提供低延迟、高带宽的实时服务。它减少数据传输时间,支持本地化处理,确保数据安全,并在无网络时仍能运作。应用于CDN、互动直播和本地服务,与云计算互补,共同优化数据处理。随着5G和IoT的发展,边缘云将在未来扮演关键角色。
|
边缘计算
阿里云最新产品手册——阿里云核心产品——边缘节点服务ENS ——边缘计算
阿里云最新产品手册——阿里云核心产品——边缘节点服务ENS ——边缘计算自制脑图界面
159 4
|
存储 运维 Kubernetes
深入浅出边缘云 | 2. 架构
深入浅出边缘云 | 2. 架构
346 0
深入浅出边缘云 | 2. 架构
|
存储 边缘计算 安全
边缘云的优势
边缘云的优势
|
边缘计算 云计算 CDN
阿里云产品体系分为6大分类——云计算基础——CDN与边缘——边缘节点服务ENS
阿里云产品体系分为6大分类——云计算基础——CDN与边缘——边缘节点服务ENS自制脑图
206 1
阿里云产品体系分为6大分类——云计算基础——CDN与边缘——边缘节点服务ENS
|
存储 弹性计算 运维
阿里云云盒:公共云的延伸部署,满足企业对灵活部署位置的要求
阿里云于2021年推出阿里云云盒,将公共云能力延伸到企业本地的数据中心或者企业指定数据中心,以满足企业对部署位置的灵活要求。
|
存储 运维 Kubernetes
深入浅出边缘云 | 1. 概述(下)
深入浅出边缘云 | 1. 概述(下)
247 0
深入浅出边缘云 | 1. 概述(下)
《阿里云产品手册2022-2023 版》——边缘网络加速
《阿里云产品手册2022-2023 版》——边缘网络加速
212 0
|
网络协议 JavaScript 应用服务中间件
深入浅出边缘云 | 3. 资源配置(上)
深入浅出边缘云 | 3. 资源配置(上)
480 0
深入浅出边缘云 | 3. 资源配置(上)