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

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Diversity in Tech Fund 来接受捐赠。

介绍

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 的全部表面积。

先决条件

要遵循本教程,您将需要:

  • 将资源部署到的 DigitalOcean 帐户。 如果您还没有,在这里注册
  • 用于执行自动化部署的 DigitalOcean API 令牌。 在此处生成一个个人访问令牌并随身携带,因为您将在第 2 步中使用它。
  • 因为您将创建和使用 Kubernetes 集群,所以您需要 安装 kubectl。 不要担心进一步配置它 - 您稍后会这样做。
  • 您将使用 TypeScript 编写基础架构即代码,因此您需要 Node.js 8 或更高版本。 在这里下载它或使用系统的包管理器安装它
  • 您将使用 Pulumi 部署基础设施,因此您需要 安装开源 Pulumi SDK
  • 要执行可选的第 5 步,您需要配置一个域名以使用 DigitalOcean 名称服务器。 本指南 解释了如何为您选择的注册商执行此操作。

第 1 步——搭建新项目的脚手架

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

首先,创建目录:

mkdir do-k8s

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

cd do-k8s

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

接下来,创建一个新的 Pulumi 项目。 有不同的方法可以完成此操作,但最简单的方法是将 pulumi new 命令与 typescript 项目模板一起使用。 该命令会首先提示你登录 Pulumi 以便保存你的项目和部署状态,然后会在当前目录下创建一个简单的 TypeScript 项目:

pulumi new typescript -y

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

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

ls

现在将出现以下文件:

OutputPulumi.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 的内容替换为以下内容:

索引.ts

import * as digitalocean from "@pulumi/digitalocean";
import * as kubernetes from "@pulumi/kubernetes";

这使得这些包的全部内容可用于您的程序。 例如,如果您使用理解 TypeScript 和 Node.js 的 IDE 键入 "digitalocean.",您应该会看到此包支持的 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 文件的末尾添加这些行:

索引.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 region sluglatest 支持的 Kubernetes 版本、s-2vcpu-2gb Droplet size slug 和状态您想要的三个 Droplet 实例的数量。 随意更改其中任何一个,但请注意,在撰写本文时,DigitalOcean Kubernetes 仅在某些地区可用。 您可以参考 产品文档 了解有关区域可用性的更新信息。

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

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

现在您已准备好部署集群。 为此,请运行 pulumi up

pulumi up

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

OutputPreviewing 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 集群。 yes/no/details 提示允许我们在实际进行任何更改之前确认这是所需的结果。 如果选择 details,将显示资源及其属性的完整列表。 选择 yes 开始部署:

OutputUpdating (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

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

OutputNAME           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 Service,这是告诉 Kubernetes 在一组目标 Pod(在本例中为上述部署)之间负载平衡访问的标准方式。

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

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

nano index.ts

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

索引.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 地址分配等的诊断。 这将帮助您了解为什么您的部署可能需要时间才能完成或卡住。

完整的输出将如下所示:

OutputUpdating (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
OutputNAME                                   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 文件的方式类似,该程序还导出 Kubernetes 服务生成的负载均衡器的 IP 地址。 使用它来 curl 端点并查看它是否已启动并正在运行:

curl $(pulumi stack output ingressIp)
Output<!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

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

OutputPreviewing 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 地址的突发奇想。 当你调整和重新部署东西时,这个地址可能会改变。 在此步骤中,您将了解如何为负载均衡器 IP 地址分配自定义 DNS 名称,以便在您随后更改基础架构时也能保持稳定。

注意: 要完成此步骤,请确保您有一个使用 DigitalOcean 的 DNS 名称服务器的域,ns1.digitalocean.comns2.digitalocean.comns3.digitalocean.com。 先决条件部分提供了配置说明。


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

索引.ts

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

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

通常需要额外的子域,例如 www,来指向 Web 应用程序。 使用 DigitalOcean DNS 记录很容易做到这一点。 为了让这个例子更有趣,还添加一个 CNAME 记录,将 www.your_domain.com 指向 your_domain.com

索引.ts

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

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

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

OutputUpdating (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

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

Output<!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 行代码和 [X256X ] 命令。

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

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

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

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

pulumi destroy

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

OutputPreviewing 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 并观察删除发生的情况:

OutputDestroying (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

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

OutputThis 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 是开源的,可以免费使用。