基础设施即代码,第二部分:用Terraform构建Docker镜像并部署到Kubernetes上

357 阅读18分钟

这个系列告诉你如何开始使用基础设施即代码(IaC)。目的是通过教程和代码实例帮助开发者建立对IaC的深刻理解。

在这篇文章中,我将演示如何为一个应用程序创建一个Docker镜像,然后将该镜像推送到Docker Hub。我还将讨论如何使用HashiCorp的Terraform创建Docker镜像并将其部署到谷歌Kubernetes引擎(GKE)集群

下面是我们在这篇文章中要完成的事情的快速清单。

  1. 建立一个新的Docker镜像
  2. 将新的Docker镜像推送到Docker Hub注册处
  3. 使用Terraform创建一个新的GKE集群
  4. 使用Terraform Kubernetes提供商创建一个新的Terraform Kubernetes部署
  5. 销毁所有使用Terraform创建的资源

注意。 在你进行这部分教程之前,请确保你已经完成了第一部分先决条件部分的所有操作。

我们的第一个任务是学习如何基于本代码库中包含的Node.js应用实例构建一个Docker镜像

构建Docker镜像

上一篇文章中,我们使用Terraform创建了一个新的GKE集群,但该集群无法使用,因为没有部署任何应用程序或服务。因为Kubernetes(K8s)是一个容器协调器,应用程序和服务必须被打包成Docker镜像,然后可以生成执行应用程序或服务的Docker容器

Docker镜像是使用docker build 命令创建的,你将需要一个Dockerfile来指定如何构建你的Docker镜像。我将讨论Dockerfile,但首先我想谈谈.dockerignore文件

什么是.dockerignore文件?

.dockerignore ,该文件排除了与它所声明的模式相匹配的文件和目录。使用这个文件有助于避免不必要地将大的或敏感的文件和目录发送给守护进程,并有可能将它们添加到公共图像中。在这个项目中,.dockerignore 文件排除了与Terraform和Node.js本地依赖有关的不必要的文件。

了解Dockerfile

Dockerfile是构建Docker镜像的关键。它指定了如何构建和配置镜像,此外还有哪些文件要导入其中。Dockerfile文件是动态的,所以你可以用不同的方式完成许多目标。重要的是,你要对Dockerfile的功能有一个扎实的了解,这样你就可以构建功能性镜像。下面是本项目代码库中包含的Dockerfile的分解。

FROM node:12

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
COPY package*.json ./

RUN npm install --only=production

# Bundle app source
COPY . .

EXPOSE 5000
CMD [ "npm", "start" ]

FROM node:12 行定义了一个要继承的镜像。当构建镜像时,Docker会继承一个父镜像。在这种情况下,它是node:12 ,如果本地不存在,就从Docker Hub中提取。

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
COPY package*.json ./

RUN npm install --only=production

这个代码块定义了WORKDIR 参数,它指定了Docker镜像中的工作目录。COPY package*.json ./ 行将任何与包相关的文件复制到Docker镜像中。RUN npm install 行安装了package.json 文件中列出的应用程序依赖项。

COPY . .

EXPOSE 5000
CMD [ "npm", "start" ]

这个代码块将所有文件复制到Docker镜像中,除了.dockerignore 文件中列出的文件和目录。EXPOSE 5000 行指定了这个Docker镜像要公开的端口。CMD [ "npm", "start" ] 行定义了如何启动这个镜像。在这种情况下,它是执行这个项目的package.json 文件中指定的start 部分。这个CMD 参数是默认的执行命令。现在你已经了解了Dockerfile,你可以用它来在本地构建一个镜像。

使用Docker build命令

使用 docker build命令,Dockerfile根据其中定义的指令建立一个新的镜像。在构建Docker镜像时,有一些命名规则需要记住。如果你打算分享镜像,命名惯例就显得尤为重要。

在我们开始构建镜像之前,我先花点时间介绍一下如何命名它们。Docker镜像使用由斜线分隔的名称组件组成的标签。因为我们将把镜像推送到Docker Hub,我们需要在镜像名称前加上我们的Docker Hub用户名。在我的例子中,是ariv3ra/ 。我通常会在后面加上项目的名称,或者对镜像的有用描述。这个Docker镜像的全名将是ariv3ra/learniac:0.0.1:0.0.1 是应用程序的一个版本标签,但你也可以用它来描述镜像的其他细节。

一旦你有了一个好的、描述性的名字,你就可以建立一个镜像。以下命令必须在项目Repo的根目录下执行(确保用你的Docker Hub名称替换ariv3ra )。

docker build -t ariv3ra/learniac  -t ariv3ra/learniac:0.0.1 .

接下来,运行这个命令,看看你机器上的Docker镜像列表。

docker images

这是我的输出。

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
ariv3ra/learniac        0.0.1               ba7a22c461ee        24 seconds ago      994MB
ariv3ra/learniac        latest              ba7a22c461ee        24 seconds ago      994MB

Docker推送命令

现在我们准备将这个镜像推送到Docker Hub,使其公开可用。Docker Hub需要授权来访问服务,所以我们需要使用login 命令来进行认证。运行这个命令来登录。

docker login

在提示中输入你的Docker Hub凭证,以授权你的账户。你只需要在每台机器上登录一次。现在你可以推送镜像了。

使用你的docker images 命令中列出的镜像名称,运行这个命令。

docker push ariv3ra/learniac

这是我的输出。

The push refers to repository [docker.io/ariv3ra/learniac]
2109cf96cc5e: Pushed 
94ce89a4d236: Pushed 
e16b71ca42ab: Pushed 
8271ac5bc1ac: Pushed 
a0dec5cb284e: Mounted from library/node 
03d91b28d371: Mounted from library/node 
4d8e964e233a: Mounted from library/node

现在你有一个Docker镜像,可以在Docker Hub中使用,并准备部署到GKE集群中。将你的应用程序部署到一个新的Kubernetes集群的所有部分都已到位。下一步是使用Terraform建立Kubernetes部署。

使用Terraform来部署Kubernetes

在本系列的第一部分,我们学习了如何使用Terraform创建一个新的谷歌Kubernetes引擎(GKE)集群。正如我之前提到的,该集群没有为任何应用程序或服务提供服务,因为我们没有向其部署任何服务。在这一节中,我将描述使用Terraform部署Kubernetes的情况。

Terraform有一个Kubernetes部署资源,可以让你定义并执行Kubernetes部署到GKE集群。在第一部分中,我们使用part01/iac_gke_cluster/ 目录中的Terraform代码创建了一个新的GKE集群。在这篇文章中,我们将分别使用part02/iac_gke_cluster/part02/iac_kubernetes_app/ 目录。iac_gke_cluster/ 是我们在第一部分中使用的相同代码。在这里,我们将结合iac_kubernetes_app/ 目录再次使用它。

Terraform Kubernetes提供商

我们之前使用Terraform谷歌云平台提供商来创建一个新的GKE集群。Terraform提供者是专门针对谷歌云平台的,但它的内部仍然是Kubernetes。因为GKE本质上是一个Kubernetes集群,我们需要使用Terraform Kubernetes提供商Kubernetes部署资源来配置和部署我们的应用程序到GKE集群。

Terraform代码文件

part02/iac_kubernetes_app/ 目录包含这些文件。

  • providers.tf
  • variables.tf
  • main.tf
  • Deployments.tf
  • services.tf
  • output.tf

这些文件维护了我们用来定义、创建和配置应用程序部署到Kubernetes集群的所有代码。接下来,我将对这些文件进行分解,让你更好地了解它们的作用。

分解: providers.tf

provider.tf 文件是我们定义我们将使用的Terraform提供者的地方:Terraform Kubernetes提供者provider.tf

provider "kubernetes" {

}

这个代码块定义了将在这个Terraform项目中使用的提供者。{ } 块是空的,因为我们将用不同的程序来处理认证要求

分解:variables.tf

这个文件应该看起来很熟悉,与第1部分variables.tf 文件类似。这个特殊的文件只指定了这个Terraform Kubernetes项目使用的输入变量。

variable "cluster" {
  default = "cicd-workshops"
}
variable "app" {
  type        = string
  description = "Name of application"
  default     = "cicd-101"
}
variable "zone" {
  default = "us-east1-d"
}
variable "docker-image" {
  type        = string
  description = "name of the docker image to deploy"
  default     = "ariv3ra/learniac:latest"
}

这个文件中定义的变量将在整个Terraform项目的项目文件的代码块中使用。所有这些变量都有default ,在执行代码时可以通过在CLI中定义它们来改变。这些变量为Terraform代码增加了急需的灵活性,并允许重复使用有价值的代码。这里需要注意的一点是,variable "docker-image" 默认参数被设置为我的Docker镜像名称。用你的Docker镜像的名字替换这个值。

分解:main.tf

main.tf 文件的元素从terraform 块开始,该块指定了Terraform Backend的类型。Terraform中的 "后端 "决定了如何加载状态以及如何执行诸如apply 的操作。这种抽象实现了非本地文件状态的存储和远程执行等功能。在这个代码块中,我们正在使用remote 后台。它使用Terraform云,并连接到你在第一部分的先决条件部分中创建的iac_kubernetes_app 工作区。

terraform {
  required_version = "~>0.12"
  backend "remote" {
    organization = "datapunks"
    workspaces {
      name = "iac_kubernetes_app"
    }
  }
}

分解:deployments.tf

接下来是对deployments.tf 文件中的语法的描述。这个文件使用Terraform Kubernetes部署资源来定义、配置和创建所有Kubernetes资源,以将我们的应用程序释放到GKE集群。

resource "kubernetes_deployment" "app" {
  metadata {
    name = var.app
    labels = {
      app = var.app
    }
  }
  spec {
    replicas = 3

    selector {
      match_labels = {
        app = var.app
      }
    }
    template {
      metadata {
        labels = {
          app = var.app
        }
      }
      spec {
        container {
          image = var.docker-image
          name  = var.app
          port {
            name           = "port-5000"
            container_port = 5000
          }
        }
      }
    }
  }
}

是时候回顾一下代码元素了,以便更好地了解正在发生的事情。

resource "kubernetes_deployment" "app" {
  metadata {
    name = var.app
    labels = {
      app = var.app
    }
  }

这个代码块指定了Terraform Kubernetes部署资源的使用,它定义了我们在Kubernetes的部署对象。metadata 块用于为Kubernetes服务中使用的参数赋值。

  spec {
    replicas = 3

    selector {
      match_labels = {
        app = var.app
      }
    }

    template {
      metadata {
        labels = {
          app = var.app
        }
      }

      spec {
        container {
          image = var.docker-image
          name  = var.app
          port {
            name           = "port-5000"
            container_port = 5000
          }
        }
      }
    }
  }

在资源spec{...} 块中,我们指定我们要在集群中为我们的应用程序运行三个Kubernetes podsselector{...} 块代表标签选择器。这些是Kubernetes中的一个核心分组原素,它将让用户选择一组对象。

资源template{...} 块中有一个spec{...} 块,它有一个container{...} 属性块。这个块有定义和配置部署中使用的容器的参数。从代码中可以看出,这里我们将定义pod的Dockerimage (我们想要使用的镜像)和容器的name ,因为它应该出现在Kubernetes中。这也是我们定义port ,以便在容器上公开,允许对运行中的应用程序进行入口访问。这些值来自同一文件夹中的variables.tf 文件。Terraform Kubernetes部署资源能够执行非常强大的配置。我鼓励你和你的团队尝试其他一些属性,以获得对工具的更广泛的熟悉。

分解:services.tf

我们已经创建了一个Terraform Kubernetes部署资源文件,并为这个应用程序定义了我们的Kubernetes部署。这就留下了一个细节来完成我们的应用程序的部署。我们要部署的应用程序是一个基本的网站。就像所有的网站一样,它需要能够被访问,才能发挥作用。在这一点上,我们的deployments.tf 文件指定了用Docker镜像部署Kubernetes pod的指令以及所需的pod数量。我们的部署还缺少一个关键因素:Kubernetes服务。这是一种抽象的方式,将运行在一组pod上的应用程序作为一个网络服务公开。有了Kubernetes,你不需要修改你的应用程序来使用一个陌生的服务发现机制。Kubernetes为pod提供了自己的IP地址和一组pod的单一DNS名称,并能在它们之间进行负载平衡。

services.tf 文件是我们定义Terraform Kubernetes服务的地方。它将连接Kubernetes元素,为我们在集群中的pod上运行的应用程序提供入口访问。下面是services.tf 文件。

resource "kubernetes_service" "app" {
  metadata {
    name = var.app
  }
  spec {
    selector = {
      app = kubernetes_deployment.app.metadata.0.labels.app
    }
    port {
      port        = 80
      target_port = 5000
    }
    type = "LoadBalancer"
  }
} 

在这一点上,描述一下spec{...} 块和其中的元素可能会有帮助。selector{ app...} 块指定了一个在deployments.tf 文件中定义的名称,并在部署资源中元数据块的label 属性中代表app 的值。这是一个重复使用相关资源中已经分配的值的例子。它还提供了一种机制,简化了重要的值,并为这样的重要数据建立了一种参考完整性。

port{...} 块有两个属性:porttarget_port 。这些参数定义了服务将监听的外部端口,以便向应用程序发出请求。在这个例子中,它是端口80。target_port 是我们的pod所监听的内部端口,即5000端口。这个服务将把所有的流量从80端口路由到5000端口。

这里要回顾的最后一个元素是type 参数,它指定了我们正在创建的服务类型。Kubernetes有三种类型的服务。在这个例子中,我们使用的是LoadBalancer 类型,它使用云提供商的负载平衡器向外部暴露服务。NodePort和ClusterIP服务,外部负载平衡器的路由会自动创建。在这种情况下,GCP将创建和配置一个LoadBalancer ,它将控制和路由流量到我们的GKE集群。

分解:output.tf

Terraform使用输出值来返回Terraform模块的值,该模块在运行terraform apply ,为子模块提供输出。这些输出用于向父模块暴露其资源属性的子集,或在CLI输出中打印某些值。output.tf 块,我们正在使用输出值来读出诸如集群名称和我们新创建的LoadBalancer服务的入口IP地址的值。这个地址是我们可以访问GKE集群上的Pod中托管的应用程序的地方。

  output "gke_cluster" {
    value = var.cluster
  }

  output "endpoint" {
    value = kubernetes_service.app.load_balancer_ingress.0.ip
  }

Initializing the Terraform part02/iac_gke_cluster

现在你对Terraform项目和语法有了更好的了解,你可以开始使用Terraform配置我们的GKE集群了。改变目录,进入part02/iac_gke_cluster 目录。

cd part02/iac_gke_cluster

当在part02/iac_gke_cluster ,运行这个命令。

terrform init

这是我的输出。

Initializing the backend...

Successfully configured the backend "remote"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "google" (hashicorp/google) 3.31.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.google: version = "~> 3.31"

Terraform has been successfully initialized!

这很好!现在我们可以创建GKE集群了。

Terraform应用part02/iac_gke_cluster

Terraform有一个命令,可以让你在不实际执行任何东西的情况下试运行和验证你的Terraform代码。该命令被称为terraform plan ,它还可以绘制出Terraform将针对你现有的基础设施执行的所有行动和变化。在终端中,运行。

terraform plan

这是我的输出。

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # google_container_cluster.primary will be created
  + resource "google_container_cluster" "primary" {
      + additional_zones            = (known after apply)
      + cluster_ipv4_cidr           = (known after apply)
      + default_max_pods_per_node   = (known after apply)
      + enable_binary_authorization = false
  ...

Terraform将根据main.tf 文件中的代码,为你创建新的GCP资源。现在,你已经准备好创建新的基础设施并部署应用程序了。在终端运行这个命令。

terraform apply

Terraform会提示你确认你的命令。输入yes ,然后按回车键。

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Terraform将在GCP上建立新的谷歌Kubernetes引擎集群。

注意集群将需要3-5分钟才能完成。这不是一个即时的过程,因为后端系统正在进行配置,并将东西带到网上。

在我的集群完成后,这是我的输出。

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

cluster = cicd-workshops
cluster_ca_certificate = <sensitive>
host = <sensitive>
password = <sensitive>
username = <sensitive>

新的GKE集群已经创建,并显示了Outputs 结果。请注意,被标记为敏感的输出值在结果中用<sensitive> 标签屏蔽了。这确保了敏感数据受到保护,但在需要时可以使用。

接下来,我们将使用part02/iac_kubernetes_app/ 目录中的代码来创建一个Kubernetes部署和附带的LoadBalancer 服务。

Terraform初始化part02/iac_kubernetes_app/。

现在我们可以使用part02/iac_kubernetes_app/ 目录中的代码将我们的应用程序部署到这个GKE集群。用这个命令改变目录进入该目录。

cd part02/iac_kubernetes_app/

当在part02/iac_kubernetes_app/ ,运行这个命令来初始化Terraform项目。

terrform init

这是我的输出。

Initializing the backend...

Successfully configured the backend "remote"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "kubernetes" (hashicorp/kubernetes) 1.11.3...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.kubernetes: version = "~> 1.11"

Terraform has been successfully initialized!

GKE集群的凭证

用Terraform创建一个google_container_cluster ,之后需要对集群进行认证。你可以使用谷歌云CLI来配置集群的访问,并生成一个kubeconfig文件。执行这个命令。

gcloud container clusters get-credentials cicd-workshops --zone="us-east1-d"

使用该命令,gcloud ,将生成一个kubeconfig条目,使用gcloud 作为认证机制。该命令使用cicd-workshops 的值作为集群名称,这也是在variables.tf 中指定的。

Terraform应用part02/iac_kubernetes_app/。

最后,我们准备使用Terraform将我们的应用程序部署到GKE集群上。执行这个命令。

terraform plan

这是我的输出。

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
Terraform will perform the following actions:
  # kubernetes_deployment.app will be created
  + resource "kubernetes_deployment" "app" {
      + id = (known after apply)
      + metadata {
          + generation       = (known after apply)
          + labels           = {
              + "app" = "cicd-101"
            }
          + name             = "cicd-101"
          + namespace        = "default"
          + resource_version = (known after apply)
          + self_link        = (known after apply)
          + uid              = (known after apply)
        }
  ...

Terraform将根据deployment.tfservices.tf 文件中的代码,为你创建新的GCP资源。现在你可以创建新的基础设施并部署应用程序了。在终端运行这个命令。

terraform apply

Terraform会提示你确认你的命令。输入yes ,然后按回车键。

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Terraform将建立你新的Kubernetes应用部署和相关的LoadBalancer。

注意集群将需要3-5分钟才能完成。这不是一个即时的过程,因为后端系统正在进行配置,并将东西上线。

在我的集群完成后,这是我的输出。

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

endpoint = 104.196.222.238
gke_cluster = cicd-workshops

现在应用程序已经部署完毕。endpoint 值,以及输出,是到集群的公共入口的IP地址LoadBalancer 。它也代表了你可以访问该应用程序的地址。打开一个网络浏览器,使用output 值来访问该应用程序。会有一个网页,上面写着 "欢迎使用CircleCI进行CI/CD 101!"。

使用Terraform销毁

你已经证明了你的Kubernetes部署是有效的,并且将应用程序部署到GKE集群已经成功测试。你可以让它继续运行,但要注意,任何在谷歌云平台上运行的资产都是有成本的,你要对这些成本负责。谷歌为其免费试用注册提供了慷慨的300美元信用额度,但如果你让资产运行,你可以很容易地吃完它。

运行terraform destroy ,将终止你在本教程中创建的任何运行资产。

运行这个命令来销毁GKE集群。

terraform destroy

请记住,上述命令只会销毁part02/iac_kubeernetes_app/ ,你需要运行以下命令来销毁本教程中创建的所有资源。

cd ../iac_gke-cluster/

terraform destroy

这将摧毁我们之前创建的GKE集群。

总结

恭喜你!你已经完成了本系列的第二部分。你已经完成了本系列教程的第二部分,通过构建和发布一个新的Docker镜像,以及使用基础设施即代码和Terraform将应用程序配置和部署到Kubernetes集群,提升了你的经验。

继续学习本教程的第三部分,在那里你将学习如何使用CircleCI将所有这些很棒的知识自动化到CI/CD管道中。

以下资源将帮助你从这里扩展你的知识。