介绍
在 Kubernetes 1.14 版本中已经 GA 了对 Windows 的支持。这一结果凝结了一群优秀的工程师的努力,他们来自微软、Pivotal、VMware、红帽以及现在已经关闭的 Apprenda 等几家公司。我在 Apprenda 工作时,不定时会为 sig-windows 社区做出一些贡献。即便现在在 Rancher Labs 任职,也一直关注它的动向。所以当公司决定在 Rancher 中增加对 Windows 支持时,我极为兴奋。
Rancher 2.3已于本月月初发布,这是首个 GA 支持 Windows 容器的 Kubernetes 管理平台。它极大降低了企业使用 Windows 容器的复杂性,并为基于 Windows 遗留应用程序的现代化提供快捷的途径——无论这些程序是在本地运行还是在多云环境中运行。此外,Rancher 2.3 还可以将它们容器化并将其转换为高效、安全和可迁移的多云应用程序,从而省去重写应用程序的工作。
在本文中,我们将在运行 RKE 的 Kubernetes 集群之上配置 Rancher 集群。我们也将会配置同时支持 Linux 和 Windows 容器的集群。完成配置后,我们将聊聊操作系统的定位,因为 Kubernetes scheduler 需要指导各种 Linux 和 Windows 容器在启动时的部署位置。
我们的目标是以完全自动化的方式执行这一操作。由于 Rancher 2.3 目前尚未 stable,因此这无法在生产环境中使用,但如果你正需要使用 Azure 和 Rancher 来完成基础架构自动化,那么这对你们的团队而言将会是一个良好的开端。退一万步而言,即使你不使用 Azure,在本例中的许多概念和代码都可以应用于其他环境中。
考虑因素
Windows vs. Linux
在我们正式开始之前,我需要告知你许多注意事项和“陷阱”。首先,最显而易见的就是:Windows 不是 Linux。Windows 新增了支持网络网格中的容器化应用程序所需的子系统,它们是 Windows 操作系统专有的,由 Windows 主机网络服务和 Windows 主机计算服务实现。操作系统以及底层容器运行时的配置、故障排除以及运维维护将会有显著区别。而且,Windows 节点收到 Windows Server licensing 的约束,容器镜像也受 Windows 容器的补充许可条款的约束。
Windows OS 版本
WindowsOS 版本需要绑定到特定的容器镜像版本,这是 Windows 独有的。使用 Hyper-V 隔离可以克服这一问题,但是从 Kubernetes 1.16 开始,Kubernetes 不支持 Hyper-V 隔离。因此,Kubernetes 和 Rancher 仅能在 Windows Server 1809/Windows Server 2019 以及 Windows Server containers Builds 17763 以及 Docker EE-basic 18.09 之前的版本中运行。
持久性支持和 CSI 插件
Kubernetes 1.16 版本以来,CSI 插件支持 alpha 版本。Windows 节点支持大量的 in-tree 和 flex volume。
CNI 插件
Rancher 支持仅限于 flannel 提供的主机网关(L2Bridge)和 VXLAN(Overlay)网络支持。在我们的方案中,我们将利用默认的 VXLAN,因为当节点并不都在同一个网络上时,主机网关选项需要 User Defined Routes 配置。这取决于提供商,所以我们将利用 VXLAN 功能的简单性。根据 Kubernetes 文档,这是 alpha 级别的支持。当前没有支持 Kubernetes 网络策略 API 的开源 Windows 网络插件。
其他限制
请确保你已经阅读了 Kubernetes 文档,因为在 Windows 容器中有许多功能无法实现,或者其功能和 Linux 中的相同功能实现方式有所不同。
Kubernetes 文档:
https://kubernetes.io/docs/setup/production-environment/windows/intro-windows-in-kubernetes/#limitations
基础架构即代码
自动化是实现 DevOps 的第一种方式。我们将自动化我们的 Rancher 集群和要在该集群中配置的 Azure 节点的基础架构。
Terraform
Terraform 是一个开源的基础架构自动化编排工具,它几乎支持所有市面上能见到的云服务提供商。今天我们将使用这一工具来自动化配置。确保你运行的 Terraform 版本至少是 Terraform 12。在本文中,使用 Terraform 版本是 v0.12.9。
$ terraform version
Terraform v0.12.9
复制代码
RKE Provider
用于 Terraform 的 RKE provider 是一个社区项目,并非由 Rancher 官方进行研发的,但包括我在内的 Rancher 的很多工程师都在使用。因为这是一个社区 provider 而不是 Terraform 官方的 provider,因此你需要安装最新版本到你的 Terraform 插件目录中。对于大部分的 Linux 发行版来说,你可以使用本文资源库中包含的 setup-rke-terraform-provider.sh 脚本。
Rancher Provider
用于 Terraform 的 Rancher2 provider 是 Terraform 支持的 provider,它通过 Rancher REST API 来自动化 Rancher。我们将用它从 Terraform 的虚拟机中创建 Kubernetes 集群,这一虚拟机需要使用 Azure Resource Manager 和 Azure Active Directory Terraform Provider 进行创建。
本例的 Format
本文中的 Terraform 模型的每个步骤都会被拆分成子模型,这将增强模型可靠性并且将来如果你创建了其他自动化架构,这些模型都可以重新使用。
Part1:设置 Rancher 集群
登录到 Azure
Azure Resource Manager 和 Azure Active Directory Terraform Provider 将使用一个激活的 Azure Cli 登录以访问 Azure。他们可以使用其他认证方法,但在本例中,我在运行 Terraform 之前先登录。
az login
Note, we have launched a browser for you to login. For old experience with device code, use "az login --use-device-code"
You have logged in. Now let us find all the subscriptions to which you have access...
[
{
"cloudName": "AzureCloud",
"id": "14a619f7-a887-4635-8647-d8f46f92eaac",
"isDefault": true,
"name": "Rancher Labs Shared",
"state": "Enabled",
"tenantId": "abb5adde-bee8-4821-8b03-e63efdc7701c",
"user": {
"name": "jvb@rancher.com",
"type": "user"
}
}
]
复制代码
设置 Resource Group
Azure Resource Group 是 Rancher 集群的节点和其他虚拟硬件存储的位置范围。我们实际上将会创建两个组,一个用于 Rancher 集群,另一个用于 Kubernetes 集群。那将在 resource-group module 中完成。
https://github.com/JasonvanBrackel/cattle-drive/tree/master/terraform-module/resourcegroup-module
resource "azurerm_resource_group" "resource-group" {
name = var.group-name
location = var.region
}
复制代码
设置硬件
虚拟网络
我们将需要一个虚拟网络和子网。我们将使用 network-module 在各自的资源组中分别设置它们。
我们将使用 node-module 设置每个节点。既然每个节点都需要安装 Docker,那么我们在使用 Rancher install-docker 脚本配置和安装 Docker 时,我们需要运行 cloud-init 文件。这个脚本将检测 Linux 发行版并且安装 Docker。
os_profile {
computer_name = "${local.prefix}-${count.index}-vm"
admin_username = var.node-definition.admin-username
custom_data = templatefile("./cloud-init.template", { docker-version = var.node-definition.docker-version, admin-username = var.node-definition.admin-username, additionalCommand = "${var.commandToExecute} --address ${azurerm_public_ip.publicIp[count.index].ip_address} --internal-address ${azurerm_network_interface.nic[count.index].ip_configuration[0].private_ip_address}" })
}
复制代码
#cloud-config
repo_update: true
repo_upgrade: all
runcmd:
- [ sh, -c, "curl https://releases.rancher.com/install-docker/${docker-version}.sh | sh && sudo usermod -a -G docker ${admin-username}" ]
- [ sh, -c, "${additionalCommand}"]
复制代码
模板中的附加命令块用这些节点的 sleep 0 填充,但是稍后该命令将用于 Linux 节点,以将 Rancher 管理的自定义集群节点加入平台。
设置节点
接下来,我们将为每个角色创建几组节点:控制平面、etcd 和 worker。我们需要考虑几件事,因为 Azure 处理其虚拟网络的方式有一些独特之处。它会保留前几个 IP 供自己使用,因此在创建静态 IP 时需要考虑这一特性。这就是在 NIC 创建中的 4 个 IP,由于我们也管理子网的 IP,因此我们在每个 IP 中都进行了处理。
resource "azurerm_network_interface" "nic" {
count = var.node-count
name = "${local.prefix}-${count.index}-nic"
location = var.resource-group.location
resource_group_name = var.resource-group.name
ip_configuration {
name = "${local.prefix}-ip-config-${count.index}"
subnet_id = var.subnet-id
private_ip_address_allocation = "static"
private_ip_address = cidrhost("10.0.1.0/24", count.index + var.address-starting-index + 4)
public_ip_address_id = azurerm_public_ip.publicIp[count.index].id
}
}
复制代码
为什么不对私有 IP 使用动态分配?
在创建并完全配置节点之前,Azure 的 Terraform provider 将无法获取 IP 地址。而通过静态处理,我们可以在生成 RKE 集群期间使用地址。当然,还有其他方法也能解决这一问题,如将基础架构配置分成多个来运行。但是为简单起见,还是对 IP 地址进行静态管理。
设置前端负载均衡器
默认情况下,Rancher 安装程序将会在每个 worker 节点安装一个 ingress controller,这意味着我们应该在可用的 worker 节点之间负载均衡任何流量。我们也将会利用 Azure 的功能来为公共 IP 创建一个公共的 DNS 入口,并且将其用于集群。这可以在 loadbalancer-module 中完成。
https://github.com/JasonvanBrackel/cattle-drive/tree/master/terraform-module/loadbalancer-module
resource "azurerm_public_ip" "frontendloadbalancer_publicip" {
name = "rke-lb-publicip"
location = var.resource-group.location
resource_group_name = var.resource-group.name
allocation_method = "Static"
domain_name_label = replace(var.domain-name-label, ".", "-")
}
复制代码
作为替代方案,其中包含使用 cloudflare DNS 的代码。我们在这篇文章中没有使用这一方案,但是你可以不妨一试。如果你使用这个方法,你将需要重置 DNS 缓存或主机文件入口,以便你的本地计算机可以调用 Rancher 来使用 Rancher terraform provider。
provider "cloudflare" {
email = "${var.cloudflare-email}"
api_key = "${var.cloudflare-token}"
}
data "cloudflare_zones" "zones" {
filter {
name = "${replace(var.domain-name, ".com", "")}.*" # Modify for other suffixes
status = "active"
paused = false
}
}
# Add a record to the domain
resource "cloudflare_record" "domain" {
zone_id = data.cloudflare_zones.zones.zones[0].id
name = var.domain-name
value = var.ip-address
type = "A"
ttl = "120"
proxied = "false"
}
复制代码
使用 RKE 安装 Kubernetes
我们将使用 Azure 和 Terraform 的动态代码块创建的节点与开源 RKE Terraform Provider 来创建一个 RKE 集群。
dynamic nodes {
for_each = module.rancher-control.nodes
content {
address = module.rancher-control.publicIps[nodes.key].ip_address
internal_address = module.rancher-control.privateIps[nodes.key].private_ip_address
user = module.rancher-control.node-definition.admin-username
role = ["controlplane"]
ssh_key = file(module.rancher-control.node-definition.ssh-keypath-private)
}
}
复制代码
使用 RKE 安装 Tiller
有很多种方式可以安装 Tiller,你可以使用 Rancher 官方文档中的方法,但是在本教程中我们将利用 RKE Add-On 的特性。
官方文档:
https://rancher.com/docs/rancher/v2.x/en/installation/ha/helm-init/
addons = <<EOL
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: tiller
namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: tiller
namespace: kube-system
subjects:
- kind: ServiceAccount
name: tiller
namespace: kube-system
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
EOL
}
复制代码
初始化 Helm
Terraform 可以运行本地脚本。既然我们将要使用 Helm 来安装 cert-manager 和 Rancher,我们需要初始化 Helm。
安装 cert-manager
这一步和 Rancher 文档中【使用 Tiller 安装 cert-manager】的内容一样。
https://rancher.com/docs/rancher/v2.x/en/installation/ha/helm-rancher/#let-s-encrypt
resource "null_resource" "install-cert-manager" {
depends_on = [null_resource.initialize-helm]
provisioner "local-exec" {
command = file("../install-cert-manager.sh")
}
}
复制代码
安装 Rancher
这一步和官方文档中【安装 Rancher】的内容一样。
https://rancher.com/docs/rancher/v2.x/en/installation/ha/helm-rancher/
install-rancher 脚本有很多个版本,我们所使用的版本要求要有 Let’s Encrypt 的证书。如果你更习惯使用自签名的证书,你需要将 install-rancher.sh 的 symlink 更改为指向其他版本,并从下面的示例代码中删除 let-encrypt 变量。
resource "null_resource" "install-rancher" {
depends_on = [null_resource.install-cert-manager]
provisioner "local-exec" {
command = templatefile("../install-rancher.sh", { lets-encrypt-email = var.lets-encrypt-email, lets-encrypt-environment = var.lets-encrypt-environment, rancher-domain-name = local.domain-name })
}
}
复制代码
Rancher 引导程序
Terraform 的 Rancher2 Provider 包含了一个引导模式。这允许我们可以设置一个 admin 密码。你可以在 rancherbootstrap-module 中看到这一步。
provider "rancher2" {
alias = "bootstrap"
api_url = var.rancher-url
bootstrap = true
insecure = true
}
resource "rancher2_bootstrap" "admin" {
provider = rancher2.bootstrap
password = var.admin-password
telemetry = true
}
复制代码
我们在这里设置集群 url。
provider "rancher2" {
alias = "admin"
api_url = rancher2_bootstrap.admin.url
token_key = rancher2_bootstrap.admin.token
insecure = true
}
resource "rancher2_setting" "url" {
provider = rancher2.admin
name = "server-url"
value = var.rancher-url
}
复制代码
Part2:设置 Rancher 管理的 Kubernetes 集群
为 Azure 创建服务主体
在我们可以使用 Azure cloud 来创建 Load Balancer 服务和 Azure 存储之前,我们需要先为 Cloud Controller Manager 配置 connector。因此,我们在 cluster-module 和 serviceprincipal-module 中创建了一个服务主体,其作用域为集群的 Resource Group。
resource "azuread_application" "ad-application" {
name = var.application-name
homepage = "https://${var.application-name}"
identifier_uris = ["http://${var.application-name}"]
available_to_other_tenants = false
}
resource "azuread_service_principal" "service-principal" {
application_id = azuread_application.ad-application.application_id
app_role_assignment_required = true
}
resource "azurerm_role_assignment" "serviceprincipal-role" {
scope = var.resource-group-id
role_definition_name = "Contributor"
principal_id = azuread_service_principal.service-principal.id
}
resource "random_string" "random" {
length = 32
special = true
}
resource "azuread_service_principal_password" "service-principal-password" {
service_principal_id = azuread_service_principal.service-principal.id
value = random_string.random.result
end_date = timeadd(timestamp(), "720h")
}
复制代码
定义自定义集群
我们需要设置 flannel 网络选项以支持 Windows flannel 驱动。你将会注意到 Azure provider 的配置。
resource "rancher2_cluster" "manager" {
name = var.cluster-name
description = "Hybrid cluster with Windows and Linux workloads"
# windows_prefered_cluster = true Not currently supported
rke_config {
network {
plugin = "flannel"
options = {
flannel_backend_port = 4789
flannel_backend_type = "vxlan"
flannel_backend_vni = 4096
}
}
cloud_provider {
azure_cloud_provider {
aad_client_id = var.service-principal.client-id
aad_client_secret = var.service-principal.client-secret
subscription_id = var.service-principal.subscription-id
tenant_id = var.service-principal.tenant-id
}
}
}
}
复制代码
创建虚拟机
这些虚拟机的创建过程与早期计算机相同,并且包含 Docker 安装脚本。这里唯一的改变是使用来自之前创建集群的 linux 节点命令的附加命令。
module "k8s-worker" {
source = "./node-module"
prefix = "worker"
resource-group = module.k8s-resource-group.resource-group
node-count = var.k8s-worker-node-count
subnet-id = module.k8s-network.subnet-id
address-starting-index = var.k8s-etcd-node-count + var.k8s-controlplane-node-count
node-definition = local.node-definition
commandToExecute = "${module.cluster-module.linux-node-command} --worker"
}
复制代码
创建 Windows Workers
Windows worker 进程类似于 Linux 进程,但有一些例外。由于 Windows 不支持 cloud-init 文件,我们需要创建一个 Windows 自定义脚本扩展。你可以在 windowsnode-module 中看到它:
https://github.com/JasonvanBrackel/cattle-drive/tree/master/terraform-module/windowsnode-module
Windows worker 使用密码进行认证,还需要 VM Agent 来运行自定义脚本扩展。
os_profile {
computer_name = "${local.prefix}-${count.index}-vm"
admin_username = var.node-definition.admin-username
admin_password = var.node-definition.admin-password
}
os_profile_windows_config {
provision_vm_agent = true
}
复制代码
加入 Rancher
节点配置完成之后,自定义脚本扩展将运行 Windows 节点命令。
注意:
这是与 Terraform 文档中不同类型的自定义脚本扩展,适用于 Linux 虚拟机。Azure 可以允许你对 Windows 节点尝试使用 Terraform 类型的扩展,但它最终会失败。
耐心一点噢
整个进程需要花费一些时间,需要耐心等待。当 Terraform 完成之后,将会有一些项目依旧在配置。即使在 Kubernetes 集群已经启动之后,Windows 节点将至少需要 10 分钟来完全初始化。正常工作的 Windows 节点看起来类似于下面的终端输出:
C:\Users\iamsuperman>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
832ef7adaeca rancher/rke-tools:v0.1.50 "pwsh -NoLogo -NonIn…" 10 minutes ago Up 9 minutes nginx-proxy
7e75dffce642 rancher/hyperkube:v1.15.4-rancher1 "pwsh -NoLogo -NonIn…" 10 minutes ago Up 10 minutes kubelet
e22b656e22e0 rancher/hyperkube:v1.15.4-rancher1 "pwsh -NoLogo -NonIn…" 10 minutes ago Up 9 minutes kube-proxy
5a2a773f85ed rancher/rke-tools:v0.1.50 "pwsh -NoLogo -NonIn…" 17 minutes ago Up 17 minutes service-sidekick
603bf5a4f2bd rancher/rancher-agent:v2.3.0 "pwsh -NoLogo -NonIn…" 24 minutes ago Up 24 minutes gifted_poincare
复制代码
Terraform 将为新平台输出凭据。
Outputs:
lets-encrypt-email = jason@vanbrackel.net
lets-encrypt-environment = production
rancher-admin-password = {REDACTED}
rancher-domain-name = https://jvb-win-hybrid.eastus2.cloudapp.azure.com/
windows-admin-password = {REDACTED}
复制代码
Part3:使用 Windows 工作负载
通过 OS 定位工作负载
因为 Windows 容器镜像和 Linux 容器镜像并不相同,我们需要使用 Kubernetes 节点的关联性来确定我们的部署目标。每个节点都有 OS 标签以帮助实现这一目的。
> kubectl get nodes
NAME STATUS ROLES AGE VERSION
control-0-vm Ready controlplane 16m v1.15.4
etcd-0-vm Ready etcd 16m v1.15.4
win-0-vm Ready worker 5m52s v1.15.4
worker-0-vm Ready worker 12m v1.15.4
> kubectl describe node worker-0-vm
Name: worker-0-vm
Roles: worker
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=worker-0-vm
kubernetes.io/os=linux
node-role.kubernetes.io/worker=true
...
> kubectl describe node win-0-vm
Name: win-0-vm
Roles: worker
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=windows
kubernetes.io/arch=amd64
kubernetes.io/hostname=win-0-vm
kubernetes.io/os=windows
复制代码
由 Rancher 2.3 部署的集群会自动使用 NoSchedule 污染 Linux worker 节点,这意味着工作负载将始终流向 Windows 节点,除非特别调度了 Linux 节点并且配置为可容忍污染。
根据计划使用集群的方式,您可能会发现,设置类似的 Windows 或 Linux 默认首选项可以在启动工作负载时减少开销。
评论