现在我们已经了解了控制循环是如何工作的,已经试验了声明性命令,并且知道了如何使用基本的Git命令,我们已经有了足够的信息来构建基本的GitOps操作符。我们现在需要创建如下三个东西:
我们将首先克隆一个Git存储库,然后从中拉出以使其与远程存储库同步。
我们将使用在Git存储库中找到的内容并尝试应用它。
我们将在一个循环中这样做,这样我们就可以对Git存储库进行更改,并且这些更改将被应用。
代码是用Go语言;这是来自Google的一种较新的语言,许多操作(ops)工具都是用它构建的,比如Docker、Terraform、Kubernetes和Argo CD。
请注意:
对于现实生活中的控制器和操作员,应该使用某些框架,例如操作员框架(https://operatorframework.io)), Kubebuilder (https://book.kubebuilder.io/),或样本控制器(https://github.com/kubernetes/sample-controller)。
我们实现的所有代码都可以在(https://github.com/PacktPublishing/ArgoCD-in-Practice/tree/main/ch01/basic-gitops-operator)中找到,而我们将应用的YAML 非标记语言(YAML)清单位于https://github.com/PacktPublishing/ArgoCD-in-Practice/tree/main/chll/basic-gitops-operator-config。
syncRepo函数接收要克隆并保持同步的存储库统一资源定位器(URL)以及执行该操作的本地路径。然后,它尝试使用go-git库(https://github.com/go-git/go-git)中的一个函数克隆存储库。如果使用git.ErrRepositoryAlreadyExists错误,这意味着我们已经克隆了存储库,我们需要从远程拉取它以获得最新的更新。这就是我们接下来要做的:在本地打开Git存储库,加载工作树,然后调用Pull方法。如果所有内容都是最新的,并且没有从远程下载内容,这个方法就会报错。因此,对于我们来说,这种情况是正常的(这是条件: if err != nil && err == git. NoErrAlreadyUpToDate )。下面的代码片段演示了该代码:
func syncRepo(repoUrl, localPath string) error {
_, err := git.PlainClone(localPath, false, &git.
CloneOptions{
URL: repoUrl,
Progress: os.Stdout,
})
if err == git.ErrRepositoryAlreadyExists {
repo, err := git.PlainOpen(localPath)
if err != nil {
return err
}
w, err := repo.Worktree()
if err != nil {
return err
}
err = w.Pull(&git.PullOptions{
RemoteName: "origin",
Progress: os.Stdout,
})
if err == git.NoErrAlreadyUpToDate {
return nil
}
return err
}
return err
}
接下来,在applyManifestsClient方法中,我有在该部分应用下载的存储库中的文件夹内容。在这里,我们在kubectl apply命令上创建了一个简单的包装器,并将我们克隆的存储库中的YAML清单所在的文件夹作为参数传递。除了使用kubectl apply命令,我们还可以将Kubernetes API与PATCH方法结合使用(带有 application/apply-patch+yaml 内容类型报头),即直接在服务器端调用apply。但这使得代码变得复杂,因为需要读取文件夹中的每个文件并将其转换为相应的Kubernetes对象,以便能够将其作为参数传递给API调用。kubectl apply命令已经完成了这一操作,因此这可能是最简单的实现。下面的代码片段演示了该代码:
func applyManifestsClient(localPath string) error {
dir, err := os.Getwd()
if err != nil {
return err
}
cmd := exec.Command("kubectl", "apply", "-f", path.Join(dir,
localPath))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
return err
}
最后,主函数是我们调用这些功能的地方,同步Git存储库,将清单应用到集群,并以5秒的间隔循环执行(为了演示目的,我使用了较短的间隔;在实际场景中,例如,Argo CD每3分钟进行一次同步)。我们定义了我们需要的变量,包括我们想要克隆的Git存储库,所以如果你要分叉它,请更新gitopsRepo值。接下来,我们调用syncRepo方法,检查是否存在错误,如果一切正常,我们继续调用applyManifestsClient。最后几行是如何在Go中使用通道实现计时器。
注:完整的代码文件
为了更好地概述,我们还添加了package和import声明;这是可以复制到 main.go文件夹中的完整实现。
这是main函数的代码,所有的东西都放在一起:
package main
import (
"fmt"
"os"
"os/exec"
"path"
"time"
"github.com/go-git/go-git/v5"
)
func main() {
timerSec := 5 * time.Second
gitopsRepo := "https://github.com/PacktPublishing/ArgoCD-inPractice.git" localPath := "tmp/"
pathToApply := "ch01/basic-gitops-operator-config"
for {
fmt.Println("start repo sync")
err := syncRepo(gitopsRepo, localPath)
if err != nil {
fmt.Printf("repo sync error: %s", err)
return
}
fmt.Println("start manifests apply")
err = applyManifestsClient(path.Join(localPath,
pathToApply))
if err != nil {
fmt.Printf("manifests apply error: %s", err)
}
syncTimer := time.NewTimer(timerSec)
fmt.Printf("\n next sync in %s \n", timerSec)
<-syncTimer.C
}
}
要使前面的代码工作,进入一个文件夹并运行以下命令(只需替换< your - username >):
go mod init github.com//basic-gitops-operator
** **这创造了一个go.mod文件,我们将存储我们需要的Go模块。然后,创建一个名为main.go的文件。复制上面的代码片段,以及syncRepo、applyManifestsClient和main这三个函数(还要添加main函数附带的包和导入声明)。然后,执行如下命令:
go get .
这将下载所有模块(不要错过最后一个点)。
最后一步是用下面的命令实际执行我们放在一起的所有内容:
go run main.go
** **应用程序开始运行后,你将注意到创建了一个t.mp文件夹,在其中,你将发现要应用到集群的清单。控制台的输出应该是这样的:
start repo sync
Enumerating objects: 36, done.
Counting objects: 100% (36/36), done.
Compressing objects: 100% (24/24), done.
Total 36 (delta 8), reused 34 (delta 6), pack-reused 0
start manifests apply
namespace/nginx created
Error from server (NotFound): error when creating "<>/argocdin-practice/ch01/basic-gitops-operator/tmp/ch01/basic-gitopsoperator-config/deployment.yaml": namespaces "nginx" not found
manifests apply error: exit status 1
next sync in 30s
start repo sync
start manifests apply
deployment.apps/nginx created
namespace/nginx unchanged
你可以看到同样的错误,因为当我们尝试应用整个文件夹时,现在也发生了同样的错误,但是在操作符第二次运行时,部署被成功创建。如果你查看你的集群,你会发现一个名为nginx的命名空间,在它里面,也有一个名为nginx的部署。你可以随意分叉存储库,并对操作符及其应用的配置进行更改。
注意:首先应用命名空间
在Argo CD中,通过首先识别名称空间并应用名称空间,解决了名称空间创建的问题。
我们创建了一个简单的GitOps操作符,显示了克隆和保持Git存储库与远程存储库同步的步骤,以及获取存储库的内容并应用它们的步骤。如果清单没有变化,那么kubectl apply命令在集群中没有什么可修改的,我们在一个循环中完成所有这些操作,该循环与本章前面介绍的控制循环非常相似。原则上,这也是Argo CD实施中发生的事情,但规模和性能都要高得多,并添加了许多功能。
你可以找到许多文章和博客文章,试图比较laC和GitOps之间的差异,以及GitOps如何在laC原则的基础上构建。我想说的是,它们有很多共同点——它们都是非常相似的做法,都使用源代码管理来存储状态。如今,当你提到laC时,你指的是通过自动化而不是手动创建基础架构的实践,并且基础架构像应用程序代码一样作为代码保存在源代码管理中。
使用laC,你希望使用管道应用更改,这比手动配置更有优势。这使我们能够在每次需要时创建相同的环境,例如,减少了阶段和生产之间不一致的数量,这将转化为开发人员花费更少的时间来调试由配置偏差导致的特殊情况和问题。
应用更改的方式可以是命令式的,也可以是声明式的;大多数工具都支持这两种方式,而有些工具本质上只是声明式的(例如Terraform或CloudFormation)。最初,一些工具是命令式的配置,但随着声明式配置最近越来越受欢迎,采用了声明式配置(请参阅https://next.redhat.com/2017/07/24/ansible-declares-declarative-intent/)。
在源代码管理中使用基础结构增加了使用将被同行评审的PR的好处,这是一个产生讨论、想法和改进的过程,直到更改被批准和合并。它还让每个人都能清楚地了解我们的基础架构更改,并可进行审核。
当我们在本章的开头讨论由应用程序交付标签创建的GitOps定义时,我们详细介绍了所有这些原则。但更重要的是,GitOps定义中还有一些不属于laC定义的东西,例如软件代理或闭环。
laC通常与CI/CD系统一起应用,从而形成一种推送模式,在这种推送模式下,你的管道
连接到你的系统(云、数据库集群、VM等)并执行更改。另一方面,GitOps是关于代理的,它们致力于协调系统状态与源代码管理中声明的状态。有一个循环,在循环中计算并应用差异,直到状态匹配。我们看到这种协调是如何反复进行的,直到没有发现更多差异,这就是实际的循环。
这在laC设置中不会发生;在讨论应用基础架构更改时,没有操作员或控制器。更新是通过推送模式完成的,这意味着GitOps拉取方式在安全性方面更好,因为它不是拥有生产凭据的管道,而是你的代理存储这些凭据,并且它可以在与生产相同的帐户中运行——或者至少在一个独立但值得信赖的账户中运行。
让代理应用你的更改也意味着GitOps只能支持声明方式。我们需要能够指定我们想要达到的状态是什么;我们无法对如何做到这一点有太多的控制权,因为我们会把负担转移到控制器和操作员身上。
以前定义为laC的工具能否以GitOps方式应用?是的,我认为我们的Terraform和Atlantis(https://www.runatlantis.io))就是一个很好的例子。这是一种在远程设置中运行代理(可能是Atlantis)的方式,因此所有命令都将不从管道执行,而是由代理执行。这意味着它确实符合GitOps的定义,但如果我们深入了解细节,我们可能会发现闭环方面存在一些不匹配之处。
在我看来,Atlantis以GitOps的方式应用基础设施变化,而如果你从管道应用Terraform,那就是laC。
因此,我们在这些实践之间没有太多差异——它们之间的联系比不同更紧密。两者都将状态存储在源代码管理中,并打开了对PR进行更改的路径。就差异而言,GitOps采用了代理和控制循环的思想,这提高了安全性,并且只能是声明式的。
本章介绍了GitOps的含义以及Kubernetes的哪些部分使其成为可能。我们检查了API服务器如何连接一切以及控制器如何工作,介绍了其中的一些控制器,并解释了它们如何在无穷无尽的控制循环中对状态变化做出反应。我们仔细研究了Kubernetes的声明性特性,从命令命令开始,然后打开路径,不仅应用文件夹,还应用Git存储库。在中,我们实现了一个非常简单的控制器,让你可以了解Argo CD的功能。
在下一章中,我们将开始探索Argo CD、其工作原理、概念和架构,以及有关同步原理的详细信息。
Kubernetes控制器体系结构:
https://kubernetes.io/docs/concepts/architecture/controller/
我们使用kubectl apply对集群进行更改,但如果你想了解如何从Go代码中使用Kubernetes API,以下是一些示例:
https://github.com/kubernetes/client-go/tree/master/examples
有关kubectl声明式配置选项的更多信息,请访问以下链接:
https://kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config/
·GitOps工作组将GitOps原则介绍为OpenGitOps:https://opengitops.dev