如何使用Pulumi管理DigitalOcean和Kubernetes基础设施

Pulumi是使用通用编程语言编写的代码创建,部署和管理基础架构的工具。在本教程中,您将配置DigitalOcean Kubernetes集群,负载平衡的Kubernetes应用程序和DigitalOcean DNS域,以使您的应用程序在您选择的域名下可用。

作者选择了技术基金多样性作为Write for DOnations计划的一部分接受捐赠。

介绍

Pulumi是使用通用编程语言编写的代码创建,部署和管理基础架构的工具。 除应用程序配置外,它还支持自动化所有DigitalOcean的托管服务,例如Droplet,托管数据库,DNS记录和Kubernetes集群。 部署是通过易于使用的命令行界面执行的,该界面还集成了各种流行的CI / CD系统。

Pulumi支持多种语言,但在本教程中,您将使用TypeScript ,这是一种使用Node.js运行时的静态类型的JavaScript版本。 这意味着您将获得IDE支持和编译时检查,这将有助于确保您配置正确的资源,使用正确的slug等,同时仍然可以访问任何NPM模块以执行实用程序任务。

在本教程中,您将配置一个DigitalOcean Kubernetes集群,一个负载均衡的Kubernetes应用程序和一个DigitalOcean DNS域,使您的应用程序可以在您选择的稳定域名下使用。 这可以全部配置为60行基础架构 - 代码和单个pulumi up命令行手势。 在本教程之后,您将准备好使用Pulumi基础架构作为代码来高效地构建强大的云架构,该代码利用DigitalOcean和Kubernetes的整个表面区域。

先决条件

要学习本教程,您需要:

第1步 - 支持新项目

第一步是创建一个存储Pulumi项目的目录。 除了描述项目及其NPM依赖项的元数据文件之外,此目录还将包含基础结构定义的源代码。

首先,创建目录:

mkdir do-k8s

接下来,进入新创建的目录:

cd do-k8s

从现在开始,从新创建的do-k8s目录运行命令。

接下来,创建一个新的Pulumi项目。 有不同的方法可以实现这一点,但最简单的方法是使用pulumi new命令和pulumi new项目模板。 此命令将首先提示您登录Pulumi,以便保存项目和部署状态,然后在当前目录中创建一个简单的TypeScript项目:

pulumi new typescript -y

在这里,您已将-y选项传递给new命令,该命令告诉它接受默认项目选项。 例如,项目名称取自当前目录的名称,因此将是do-k8s 如果您想为项目名称使用不同的选项,只需忽略-y

运行该命令后,使用ls列出目录的内容:

ls

现在将出现以下文件:

Pulumi.yaml       index.ts          node_modules
package-lock.json package.json      tsconfig.json

您要编辑的主文件是index.ts 虽然本教程仅使用此单个文件,但您可以使用Node.js模块以您认为合适的方式组织项目。 本教程还一次描述了一个步骤,利用Pulumi可以检测并逐步部署已更改内容的事实。 如果您愿意,您可以只填充整个程序,并使用pulumi up一次性部署它。

现在您已经构建了新项目,您已准备好添加遵循本教程所需的依赖项。

第2步 - 添加依赖项

下一步是在DigitalOcean和Kubernetes包上安装和添加依赖项。 首先,使用NPM安装它们:

npm install @pulumi/digitalocean @pulumi/kubernetes

这将下载NPM包,Pulumi插件,并将它们保存为依赖项。

接下来,使用您喜欢的编辑器打开index.ts文件。 本教程将使用nano:

nano index.ts

用以下内容替换index.ts的内容:

index.ts
import * as digitalocean from "@pulumi/digitalocean";
import * as kubernetes from "@pulumi/kubernetes";

这使得这些程序包的完整内容可供您的程序使用。 如果键入"digitalocean." 例如,使用了解TypeScript和Node.js的IDE,您应该会看到此软件包支持的DigitalOcean资源列表。

添加内容后保存并关闭文件。

注意:我们将使用这些包中可用内容的子集。 有关资源,属性和相关API的完整文档,请参阅@pulumi/digitalocean@pulumi/kubernetes软件包的相关API文档。

接下来,您将配置您的DigitalOcean令牌,以便Pulumi可以在您的帐户中配置资源:

pulumi config set digitalocean:token YOUR_TOKEN_HERE --secret

注意--secret标志,它使用Pulumi的加密服务来加密你的令牌,确保它存储在密文中。 如果您愿意,可以使用DIGITALOCEAN_TOKEN环境变量,但每次更新程序时都需要记住设置它,而使用配置会自动存储并将其用于您的项目。

在此步骤中,您添加了必要的依赖项并使用Pulumi配置了API令牌,以便您可以置备Kubernetes集群。

第3步 - 配置Kubernetes集群

现在您已准备好创建DigitalOcean Kubernetes集群。 重新打开index.ts文件开始:

nano index.ts

index.ts文件的末尾添加这些行:

index.ts
…
const cluster = new digitalocean.KubernetesCluster("do-cluster", {
    region: digitalocean.Regions.SFO2,
    version: "latest",
    nodePool: {
        name: "default",
        size: digitalocean.DropletSlugs.DropletS2VPCU2GB,
        nodeCount: 3,
    },
});

export const kubeconfig = cluster.kubeConfigs[0].rawConfig;

这个新代码分配了一个digitalocean.KubernetesCluster实例,并在其上设置了许多属性。 这包括使用sfo2 区域sluglatest支持的Kubernetes版本, s-2vcpu-2gb Droplet大小slug ,并说明你想要的三个Droplet实例的数量。 请随意更改其中任何一项,但请注意,DigitalOcean Kubernetes仅在撰写本文时在某些地区提供。 您可以参考产品文档以获取有关区域可用性的更新信息。

有关可在群集上配置的完整属性列表,请参阅KubernetesCluster API文档

该代码段中的最后一行导出生成的Kubernetes集群的kubeconfig文件,以便于使用。 导出的变量将打印到控制台,也可供工具访问。 您将使用此标准从kubectl等标准工具访问我们的集群。

现在您已准备好部署群集。 要这样做,请运行pulumi up

pulumi up

此命令获取程序,生成用于创建所述基础结构的计划,并执行一系列步骤来部署这些更改。 这适用于初始创建基础结构,此外还可以在进行后续更新时对您的基础结构进行区分和更新。 在这种情况下,输出将如下所示:

Previewing update (dev):

     Type                                     Name        Plan
 +   pulumi:pulumi:Stack                      do-k8s-dev  create
 +   └─ digitalocean:index:KubernetesCluster  do-cluster  create

Resources:
    + 2 to create

Do you want to perform this update?
  yes
> no
  details

这表示继续更新将创建一个名为do-cluster Kubernetes do-cluster yes/no/details提示允许我们在实际进行任何更改之前确认这是期望的结果。 如果选择details ,将显示资源及其属性的完整列表。 选择“ yes开始部署:

Updating (dev):

     Type                                     Name        Status
 +   pulumi:pulumi:Stack                      do-k8s-dev  created
 +   └─ digitalocean:index:KubernetesCluster  do-cluster  created

Outputs:
    kubeconfig: "…"

Resources:
    + 2 created

Duration: 6m5s

Permalink: https://app.pulumi.com/…/do-k8s/dev/updates/1

创建群集需要几分钟,但随后它将启动并运行,并且完整的kubeconfig将打印到控制台。 kubeconfig保存到文件:

pulumi stack output kubeconfig > kubeconfig.yml

然后使用它与kubectl执行任何Kubernetes命令:

KUBECONFIG=./kubeconfig.yml kubectl get nodes

您将收到类似于以下内容的输出:

NAME           STATUS    ROLES     AGE       VERSION
default-o4sj   Ready     <none>    4m5s      v1.14.2
default-o4so   Ready     <none>    4m3s      v1.14.2
default-o4sx   Ready     <none>    3m37s     v1.14.2

此时,您已设置基础架构作为代码,并具有可重复的方式来启动和配置新的DigitalOcean Kubernetes集群。 在下一步中,您将在此基础上构建以在代码中定义Kubernetes基础结构,并学习如何以类似方式部署和管理它们。

第4步 - 将应用程序部署到群集

接下来,您将使用基础架构作为代码来描述Kubernetes应用程序的配置。 这将包括三个部分:

  1. 一个Provider对象,它告诉Pulumi将Kubernetes资源部署到DigitalOcean集群,而不是kubectl配置使用的默认值。
  2. Kubernetes部署 ,是部署Docker容器映像的标准Kubernetes方法,可以在任意数量的Pod上复制。
  3. Kubernetes服务 ,这是告诉Kubernetes跨目标Pod集合进行负载均衡访问的标准方法(在本例中为上面的部署)。

这是一个相当标准的参考架构,用于在Kubernetes中启动和运行负载均衡服务。

要部署所有这三个,请再次打开index.ts文件:

nano index.ts

文件打开后,将此代码附加到文件末尾:

index.ts
…
const provider = new kubernetes.Provider("do-k8s", { kubeconfig })

const appLabels = { "app": "app-nginx" };
const app = new kubernetes.apps.v1.Deployment("do-app-dep", {
    spec: {
        selector: { matchLabels: appLabels },
        replicas: 5,
        template: {
            metadata: { labels: appLabels },
            spec: {
                containers: [{
                    name: "nginx",
                    image: "nginx",
                }],
            },
        },
    },
}, { provider });
const appService = new kubernetes.core.v1.Service("do-app-svc", {
    spec: {
        type: "LoadBalancer",
        selector: app.spec.template.metadata.labels,
        ports: [{ port: 80 }],
    },
}, { provider });

export const ingressIp = appService.status.loadBalancer.ingress[0].ip;

此代码与标准Kubernetes配置类似,对象及其属性的行为是等效的,除了它与您的其他基础结构声明一起使用TypeScript编写。

进行更改后保存并关闭文件。

就像之前一样,运行pulumi up进行预览,然后部署更改:

pulumi up

选择“ yes继续后,CLI将打印出详细的状态更新,包括有关Pod可用性,IP地址分配等的诊断。 这将有助于您了解部署可能需要时间来完成或卡住的原因。

完整输出将如下所示:

Updating (dev):

     Type                            Name        Status
     pulumi:pulumi:Stack             do-k8s-dev
 +   ├─ pulumi:providers:kubernetes  do-k8s      created
 +   ├─ kubernetes:apps:Deployment   do-app-dep  created
 +   └─ kubernetes:core:Service      do-app-svc  created

Outputs:
  + ingressIp : "157.230.199.202"

Resources:
    + 3 created
    2 unchanged

Duration: 2m52s

Permalink: https://app.pulumi.com/…/do-k8s/dev/updates/2

完成后,请注意所需的Pod数正在运行:

KUBECONFIG=./kubeconfig.yml kubectl get pods
NAME                                   READY     STATUS    RESTARTS   AGE
do-app-dep-vyf8k78z-758486ff68-5z8hk   1/1       Running   0          1m
do-app-dep-vyf8k78z-758486ff68-8982s   1/1       Running   0          1m
do-app-dep-vyf8k78z-758486ff68-94k7b   1/1       Running   0          1m
do-app-dep-vyf8k78z-758486ff68-cqm4c   1/1       Running   0          1m
do-app-dep-vyf8k78z-758486ff68-lx2d7   1/1       Running   0          1m

与程序导出群集的kubeconfig文件的方式kubeconfig ,此程序还会导出Kubernetes服务生成的负载均衡器的IP地址。 使用它来curl端点并看到它已启动并运行:

curl $(pulumi stack output ingressIp)
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

从这里,您可以轻松编辑和重新部署应用程序基础结构。 例如,尝试更改replicas: 5行说replicas: 7 ,然后重新运行pulumi up

pulumi up

请注意,它只显示已更改的内容,并且选择详细信息会显示精确的差异:

Previewing update (dev):

     Type                           Name        Plan       Info
     pulumi:pulumi:Stack            do-k8s-dev
 ~   └─ kubernetes:apps:Deployment  do-app-dep  update     [diff: ~spec]

Resources:
    ~ 1 to update
    4 unchanged

Do you want to perform this update? details
  pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:dev::do-k8s::pulumi:pulumi:Stack::do-k8s-dev]
    ~ kubernetes:apps/v1:Deployment: (update)
        [id=default/do-app-dep-vyf8k78z]
        [urn=urn:pulumi:dev::do-k8s::kubernetes:apps/v1:Deployment::do-app-dep]
        [provider=urn:pulumi:dev::do-k8s::pulumi:providers:kubernetes::do-k8s::80f36105-337f-451f-a191-5835823df9be]
      ~ spec: {
          ~ replicas: 5 => 7
        }

现在,您拥有一个功能齐全的Kubernetes集群和一个正常运行的应用程序。 启动并运行应用程序后,您可能需要配置要与应用程序一起使用的自定义域。 下一步将指导您使用Pulumi配置DNS。

第5步 - 创建DNS域(可选)

虽然Kubernetes集群和应用程序已启动并运行,但应用程序的地址取决于集群自动分配IP地址的想法。 在调整和重新部署内容时,此地址可能会发生变化。 在此步骤中,您将看到如何将自定义DNS名称分配给负载均衡器IP地址,以便即使您随后更改基础结构也能保持稳定。

注意:要完成此步骤,请确保使用DigitalOcean的DNSNameserversns1.digitalocean.comns2.digitalocean.comns3.digitalocean.com 有关配置的说明,请参阅“先决条件”部分。

要配置DNS,请打开index.ts文件并将以下代码附加到文件末尾:

index.ts
…
const domain = new digitalocean.Domain("do-domain", {
    name: "your_domain",
    ipAddress: ingressIp,
});

此代码创建一个带有A记录的新DNS条目,该记录引用您的Kubernetes服务的IP地址。 将此代码段中的your_domain替换为您选择的域名。

通常需要其他子域(如www )指向Web应用程序。 使用DigitalOcean DNS记录很容易实现。 要使此示例更有趣,还要添加指向www. your_domain .comCNAME记录www. your_domain .com www. your_domain .comyour_domain .com

index.ts
…
const cnameRecord = new digitalocean.DnsRecord("do-domain-cname", {
    domain: domain.name,
    type: "CNAME",
    name: "www",
    value: "@",
});

进行这些更改后保存并关闭文件。

最后,运行pulumi up以部署DNS更改以指向现有应用程序和集群:

Updating (dev):

     Type                             Name             Status
     pulumi:pulumi:Stack              do-k8s-dev
 +   ├─ digitalocean:index:Domain     do-domain        created
 +   └─ digitalocean:index:DnsRecord  do-domain-cname  created

Resources:
    + 2 created
    5 unchanged

Duration: 6s

Permalink: https://app.pulumi.com/…/do-k8s/dev/updates/3

传播DNS更改后,您将能够在自定义域中访问您的内容:

curl www.your_domain.com

您将收到类似于以下内容的输出:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

有了这个,您已经成功建立了一个新的DigitalOcean Kubernetes集群,为其部署了一个负载均衡的Kubernetes应用程序,并使用DigitalOcean DNS为该应用程序的负载均衡器提供了一个稳定的域名,所有这些都在60行代码和一个pulumi up命令中。

如果您不再需要资源,下一步将指导您删除资源。

第6步 - 删除资源(可选)

在结束本教程之前,您可能希望销毁上面创建的所有资源。 这将确保您不会因未使用的资源而被收取费用。 如果您希望保持应用程序正常运行,请跳过此步骤。

运行以下命令以销毁资源。 小心使用它,因为它无法撤消!

pulumi destroy

up命令一样, destroy在执行操作之前会显示预览和提示:

Previewing destroy (dev):

     Type                                     Name             Plan
 -   pulumi:pulumi:Stack                      do-k8s-dev       delete
 -   ├─ digitalocean:index:DnsRecord          do-domain-cname  delete
 -   ├─ digitalocean:index:Domain             do-domain        delete
 -   ├─ kubernetes:core:Service               do-app-svc       delete
 -   ├─ kubernetes:apps:Deployment            do-app-dep       delete
 -   ├─ pulumi:providers:kubernetes           do-k8s           delete
 -   └─ digitalocean:index:KubernetesCluster  do-cluster       delete

Resources:
    - 7 to delete

Do you want to perform this destroy?
  yes
> no
  details

假设这是你想要的,选择yes并观察删除发生:

Destroying (dev):

     Type                                     Name             Status
 -   pulumi:pulumi:Stack                      do-k8s-dev       deleted
 -   ├─ digitalocean:index:DnsRecord          do-domain-cname  deleted
 -   ├─ digitalocean:index:Domain             do-domain        deleted
 -   ├─ kubernetes:core:Service               do-app-svc       deleted
 -   ├─ kubernetes:apps:Deployment            do-app-dep       deleted
 -   ├─ pulumi:providers:kubernetes           do-k8s           deleted
 -   └─ digitalocean:index:KubernetesCluster  do-cluster       deleted

Resources:
    - 7 deleted

Duration: 7s

Permalink: https://app.pulumi.com/…/do-k8s/dev/updates/4

在这一点上,什么都没有:DNS条目消失了,Kubernetes集群 - 以及在其中运行的应用程序 - 都消失了。 永久链接仍然可用,因此您仍然可以返回并查看此的完整更新历史记录。 如果破坏是一个错误,这可以帮助您恢复,因为该服务保留所有资源的完整状态历史记录。

如果您想要完整地销毁项目,请删除:

pulumi stack rm

您将收到输出,要求您通过键入的名称来确认删除:

This will permanently remove the 'dev' stack!
Please confirm that this is what you'd like to do by typing ("dev"):

与删除云基础架构资源的destroy命令不同,删除会从Pulumi的权限中完全删除的完整历史记录。

结论

在本教程中,除了使用此群集的Kubernetes应用程序配置外,您还部署了DigitalOcean基础架构资源 - Kubernetes群集和具有A和CNAME记录的DNS域。 您已经使用熟悉的编程语言TypeScript编写的基础架构代码完成了这项工作,该编程语言与现有的编辑器,工具和库一起使用,并利用现有的社区和软件包。 您已使用单个命令行工作流完成所有这些工作,以执行跨应用程序和基础结构的部署。

从这里开始,您可以采取以下几个步骤:

GitHub上提供了本教程的全部示例。 有关如何在您自己的项目中使用Pulumi基础架构作为代码的详细信息,请查看Pulumi文档教程入门指南。 Pulumi是开源的,可以免费使用。