当前位置:   article > 正文

aws eks中amazonlinux2023版本节点加入集群逻辑变更的测试

aws eks中amazonlinux2023版本节点加入集群逻辑变更的测试

基于amazonlinux2023的EKS优化AMI相关更新如下

  • 引入yaml文件进行节点的初始化配置

  • 需要VPC CNI v1.16.2及以上版本

  • 可配置实例存储将自动挂载为 raid0

  • 需要IMDSv2

  • 使用cgroupv2

从userdata到nodeadm

AL2中节点需要通过bootstrap.sh脚本执行节点的初始化逻辑,在AL2023中userdata中的内容变更为MIME格式的yaml文件,该文件的解析和配置是通过nodeadm完成的

在**amazon-eks-ami**构建仓库中的al2023文件中,从install-worker.sh和install-nodeadm.sh脚本中可以看到AMI中内置的组件和systemd service。节点启动运行的service包括以下两个和nodeadm相关的service,分别是nodeadm-config和nodeadm-run

# enable nodeadm bootstrap systemd units
sudo systemctl enable nodeadm-config nodeadm-run
  • 1
  • 2

从以下service中可以,nodeadm主要包括config和run两部分逻辑

# Before=cloud-init.service
/usr/bin/nodeadm init --skip run

# After=nodeadm-config.service cloud-final.service
# Requires=nodeadm-config.service
/usr/bin/nodeadm init --skip config 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

手动执行node init命令结果如下,脚本必须以root用户运行

image.png

基于以上的日志信息,我们需要明确以下几个问题

  1. MIME文件是如何被读取和解析的?
  2. nodeadm如何替代bootstrap脚本完成节点的初始化工作(包括配置文件处理,进程的启动和系统配置)?
  3. nodeadm如何进行troubleshooting?

nodeadm代码分析

接下来通过查看nodeadm代码解答上述问题

nodeadm入口文件为cmd/nodeadm/main.go使用了flaggy作为cmd命令解析库,在入口中初始化config和init配置项,之后遍历并分别运行子命令

cmds := []cli.Command{
		config.NewConfigCommand(),
		initcmd.NewInitCommand(),
	}
for _, cmd := range cmds {
    if cmd.Flaggy().Used {
        err := cmd.Run(log, opts)
       ...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在Run方法中,统一通过BuildConfigProvider获取nodeconfig

provider, err := configprovider.BuildConfigProvider(opts.ConfigSource)
  • 1

之后根据配置的source URL获取raw schema,默认走imds获取imds://user-data的数据

switch parsedURL.Scheme {
	case "imds":
		return NewUserDataConfigProvider(), nil
	case "file":
		source := getURLWithoutScheme(parsedURL)
		return NewFileConfigProvider(source), nil
	default:
		return nil, fmt.Errorf("unsupported scheme: %s", parsedURL.Scheme)
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

初始化imds客户端后,按照以下顺序获取数据,如果无法将MIME解析为multipart document,则整个userdata会被直接解析为node config,所以逻辑上我们可以直接写nodeconfig yaml文件

func (ics *userDataConfigProvider) Provide() (*internalapi.NodeConfig, error) {
	userData, err := ics.getUserData() // 获取userdata
	if multipartReader, err := getMIMEMultipartReader(userData); err == nil {
		config, err := parseMultipart(multipartReader) // 解码MIME文件
		...
	} else {
		config, err := apibridge.DecodeNodeConfig(userData)
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在解码函数中,使用了DecodeNodeConfig函数,这里会比较MIME文件的类型是否为"application/" + “node.eks.aws”,从这里我们得知MIME文件中cloud-init,bash脚本和nodeconfig可以共存的原因,前两者并不会被nodeadm解析和处理,并且顺序在cloud-init之后

if mediaType == nodeConfigMediaType {
        nodeConfigPart, err := io.ReadAll(part)
    	// DecodeNodeConfig unmarshals the given data into an internal NodeConfig object. The data may be JSON or YAML.
        decodedConfig, err := apibridge.DecodeNodeConfig(nodeConfigPart) // 解析为golang对象,
        nodeConfigs = append(nodeConfigs, decodedConfig)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在将yaml文件解码并最终返回NodeConfig对象,从doc中我们可以从api中查看到能够填写的参数,和官方配置清单基本是一致的,例如ClusterDetails

type ClusterDetails struct {
	Name                 string `json:"name,omitempty"`
	APIServerEndpoint    string `json:"apiServerEndpoint,omitempty"`
	CertificateAuthority []byte `json:"certificateAuthority,omitempty"`
	CIDR                 string `json:"cidr,omitempty"`
	EnableOutpost        *bool  `json:"enableOutpost,omitempty"`
	ID                   string `json:"id,omitempty"`
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

获取nodeconfig之后,开始进行如下两部分操作

  • containerd和kubelet相关进程配置和启动
  • 系统方面的配置和启动

在containerd和kubelet部分入口如下,实现了4个方法意思很容易明白,主要配置逻辑在Configure中

daemons := []daemon.Daemon{
    containerd.NewContainerdDaemon(daemonManager),
    kubelet.NewKubeletDaemon(daemonManager),
}

func (cd *containerd) Configure(c *api.NodeConfig) error {
	return writeContainerdConfig(c)
}

func (cd *containerd) EnsureRunning() error {
	return cd.daemonManager.StartDaemon(ContainerdDaemonName)
}

func (cd *containerd) PostLaunch(c *api.NodeConfig) error {
	return cacheSandboxImage(c)
}

func (cd *containerd) Name() string {
	return ContainerdDaemonName
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

对于containerdconfigure,主要为加载cfg并写入/etc/containerd/config.toml

#internal/containerd/config.go
containerdConfig, err := generateContainerdConfig(cfg)
if err := util.WriteFileWithDir(containerdConfigFile, containerdConfig, containerdConfigPerm);
  • 1
  • 2
  • 3

对于kubeletconfigure,主要为如下配置,执行逻辑和al2中的bootstrap基本一致,即基于原始模板做变量替换并写入指定路径

if err := k.writeKubeletConfig(cfg); err != nil {
if err := k.writeKubeconfig(cfg); err != nil {
if err := k.writeImageCredentialProviderConfig(cfg); err != nil {
if err := writeClusterCaCert(cfg.Spec.Cluster.CertificateAuthority); err != nil {
if err := k.writeKubeletEnvironment(cfg); err != nil {
  • 1
  • 2
  • 3
  • 4
  • 5

配置完毕后通过以下代码启动daemon,例如kubelet

const KubeletDaemonName = "kubelet"
if err := daemon.EnsureRunning();
func (k *kubelet) EnsureRunning() error {
	return k.daemonManager.StartDaemon(KubeletDaemonName)
}
func (m *systemdDaemonManager) StartDaemon(name string) error {
	unitName := getServiceUnitName(name)
	_, err := m.conn.StartUnitContext(context.TODO(), unitName, ModeReplace, nil)
	return err
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在系统配置下如果指定了localstorage相关配置就会执行命令,setup-local-disks这个命令是预置的shell脚本$ cat /usr/bin/setup-local-disks

cmd := exec.Command("setup-local-disks", strategy)
  • 1

nodeconfig配置实践

按照al2中的启动逻辑,如果指定启动模板但不指定ami,则最终的userdata中会出现2部分MIME文件。使用如下userdata的启动模板创建节点组

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    name: test127
--//--
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

最终生效的模板如下,说明不选择ami时最终还是会拼接

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    apiServerEndpoint: https://F4B054xxxxxxxxx9A8CA3EE4.yl4.cn-north-1.eks.amazonaws.com.cn
    certificateAuthority: LS0tLS1CRUdJTiBS0K
    cidr: 10.100.0.0/16
    name: test127
  kubelet:
    config:
      maxPods: 17
      clusterDNS:
      - 10.100.0.10
    flags:
    - "--node-labels=eks.amazonaws.com/sourceLaunchTemplateVersion=7,eks.amazonaws.com/nodegroup-image=ami-0efa79cf5795c3dfa,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup=tmp2,eks.amazonaws.com/sourceLaunchTemplateId=lt-0cf3f3c96601398d5"

--//
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    name: test127
--//--
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

如果选择ami呢?显然没有拼接,但是实例没有加入集群,查看日志发现报错

Mar 06 04:33:30 localhost nodeadm[1445]: {"level":"fatal","ts":1709699610.6430151,"caller":"nodeadm/main.go:36","msg":"Command failed","error":"Apiserver endpoint is missing in cluster configuration","stacktrace":"main.main\n\t/workdir/cmd/nodeadm/mai>
Mar 06 04:33:30 localhost systemd[1]: nodeadm-config.service: Main process exited, code=exited, status=1/FAILURE
Mar 06 04:33:30 localhost systemd[1]: nodeadm-config.service: Failed with result 'exit-code'.
Mar 06 04:33:30 localhost systemd[1]: Failed to start nodeadm-config.service - EKS Nodeadm Config.
  • 1
  • 2
  • 3
  • 4

这是由于没有配置apiserver终端节点导致配置校验失败导致的,和bootstrap不同,仅仅指定cluster name是不够的,参考nodeadm文档中nodeconfig的最小参数要求应当如下

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    name: my-cluster
    apiServerEndpoint: https://example.com
    certificateAuthority: Y2VydGlmaWNhdGVBdXRob3JpdHk=
    cidr: 10.100.0.0/16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我们尝试将启动模板的userdata部分修改如下,使其直接退化为不解析MIME格式文件模式,测试也能够顺利加入集群

apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    apiServerEndpoint: https://F4B054xxxxxxxxx9A8CA3EE4.yl4.cn-north-1.eks.amazonaws.com.cn
    certificateAuthority: LS0tLS1CRUdJTiBDRVJUSUZJQ0RFLS0tLS0K
    cidr: 10.100.0.0/16
    name: test127
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

既然如此我们可以不从imds而是从file中读取配置文件(此时已经不需要解码),而是手动指定sudo nodeadm init -c file://nodeconfig.yaml,节点成功能够加入集群

# nodeconfig.yaml
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    apiServerEndpoint: https://F4B054xxxxxxxxx9A8CA3EE4.yl4.cn-north-1.eks.amazonaws.com.cn
    certificateAuthority: LS0tLS1CRUdJTiBDRVJUSxkFRFLS0tLS0K
    cidr: 10.100.0.0/16
    name: test127
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果加入自定义的bash脚本呢?按照之前的配置方式,取消ami的选择,然后加入以下userdata

Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
/bin/echo "Hello World" >> /tmp/testfile.txt
--//--
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

最终的userdata生效如下,节点顺利加入集群,并且脚本顺利执行。需要注意的是两者的执行现后顺序

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    apiServerEndpoint: https://F4B054xxxxxxxxx9A8CA3EE4.yl4.cn-north-1.eks.amazonaws.com.cn
    certificateAuthority: LS0tLS1CRUdJTiBDRVJUSUZJQLS0tLS0K
    cidr: 10.100.0.0/16
    name: test127
  kubelet:
    config:
      maxPods: 17
      clusterDNS:
      - 10.100.0.10
    flags:
    - "--node-labels=eks.amazonaws.com/sourceLaunchTemplateVersion=11,eks.amazonaws.com/nodegroup-image=ami-0efa79cf5795c3dfa,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup=tmp5,eks.amazonaws.com/sourceLaunchTemplateId=lt-0cf3f3c96601398d5"

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
/bin/echo "Hello World" >> /tmp/testfile.txt
--//--
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

完整的启动配置如下,需要注意的是

  • 配置对象将按照在 MIME 多部分文档中出现的顺序进行合并最后一个配置对象中的值优先级最高
  • 内联containerd的配置优先级最高
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    name: my-cluster
    apiServerEndpoint: https://example.com
    certificateAuthority: Y2VydGlmaWNhdGVBdXRob3JpdHk=
    cidr: 10.100.0.0/16
  # 下面部分是可选的
  kubelet:
    config:
      maxPods: 17
      clusterDNS:
      - 10.100.0.10
    flags:
    - "--node-labels=eks.amazonaws.com/nodegroup-image=ami-0efa79cf5795c3dfa,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup=testsimple"
  containerd:
    config: |
      [plugins."io.containerd.grpc.v1.cri".containerd]
      discard_unpacked_layers = false
  instance:
  	localStorage:
  		strategy: RAID0
--//--
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

一些相关的子命令

开启debug模式日志

$ sudo nodeadm config check  --development
2024-03-06T05:05:42.839Z        INFO    config/check.go:27      Checking configuration  {"source": "imds://user-data"}
2024-03-06T05:05:42.841Z        INFO    config/check.go:36      Configuration is valid
[ec2-user@ip-192-168-9-133 log]$ sudo nodeadm init --development
2024-03-06T05:05:57.881Z        INFO    init/init.go:49 Checking user is root..
  • 1
  • 2
  • 3
  • 4
  • 5

配置校验

# /usr/bin/nodeadm config check
{"level":"info","ts":1709623740.1376612,"caller":"config/check.go:27","msg":"Checking configuration","source":"imds://user-data"}
{"level":"info","ts":1709623740.1424549,"caller":"config/check.go:36","msg":"Configuration is valid"}
  • 1
  • 2
  • 3

总的来说,nodeadm替代bootstrap脚本完成了节点的初始化工作,通过有限的配置项降低了节点配置的难度和错误概率。

调试配置

如果需要断点调试,vscode中nodeadm的调试配置如下

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch Package",
            "type": "go",
            "request": "launch",
            "mode": "debug",
            "program": "${fileDirname}",
            "args": ["init"]
        }
    ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/616929
推荐阅读
相关标签
  

闽ICP备14008679号