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

从以下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 

手动执行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)
       ...
    }
}

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

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

之后根据配置的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)
	}

初始化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)
	}
}

在解码函数中,使用了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)
    }
}

在将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"`
}

获取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
}

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

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

对于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 {

配置完毕后通过以下代码启动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
}

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

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

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
--//--

最终生效的模板如下,说明不选择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
--//--

如果选择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.

这是由于没有配置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

我们尝试将启动模板的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

既然如此我们可以不从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

如果加入自定义的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
--//--

最终的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
--//--

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

  • 配置对象将按照在 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
--//--

一些相关的子命令

开启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..

配置校验

# /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"}

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

调试配置

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

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch Package",
            "type": "go",
            "request": "launch",
            "mode": "debug",
            "program": "${fileDirname}",
            "args": ["init"]
        }
    ]
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/646559.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【学习笔记】Windows GDI绘图(七)图形路径GraphicsPath详解(下)

文章目录 前三篇回顾GraphicsPath方法Flatten压平(将曲线转成线段)GetBounds获取外接矩形GetLastPoint获取路径最后一个点IsOutlineVisibleIsVisiable是否在轮廓上或内部Reset重置Reverse逆转点的顺序Transform矩阵变换Wrap扭曲变换Widen将路径替换为指定画笔的填充区域 前三篇…

大白话DC3算法

DC3算法是什么 DC3算法(也称为Skew算法)是一种高效的构建后缀数组的算法,全称为Difference Cover Modulo 3算法。 该算法于2002年被提出,论文参考: https://www.cs.cmu.edu/~guyb/paralg/papers/KarkkainenSanders0…

与MySQL DDL 对比分析OceanBase DDL的实现

本文将简要介绍OceanBase的DDL实现方式,并通过与MySQL DDL实现的对比,帮助大家更加容易理解。 MySQL DDL 的算法 MySQL 的DDL实现算法主要有 copy、inplace和instant。 copy copy算法的实现相对简单,MySQL首先会创建一个临时表&#xff0…

运算放大器学习笔记

运放简介 运放参数 读懂运放生产商提供的datasheet 是用好一款运放的先决条件 dataSheet 下载地址: 各芯片厂家官网http://www.ic37.comhttps://www.szlcsc.comhttp://www.alldatasheet.com 参数1: 供电电压Vs 某些运放可以用单电源供电某些运放需要…

计算机网络基本概念

文章目录 情景带入一些基本概念网络网络编程:7层网络模型OSI:TCP/IP Protocol Architecture Layers与OSI的对应关系SocketClient-Server Application报文段:传输协议:Mac地址IP地址端口URL 情景带入 随着时代的发展,我…

JVM之【类加载机制】

一、类加载过程 1. 加载(Loading) 工作内容: 通过类的全限定名来获取定义此类的二进制字节流: JVM首先会调用类加载器的findClass方法来找到类文件的路径,通常从文件系统、JAR包、网络、数据库等来源获取类文件。 将…

嵌入式进阶——数码管2

🎬 秋野酱:《个人主页》 🔥 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 驱动封装封装的一些疑问数字走马灯实现扩展知识 驱动封装 根据前面的内容可以将代码进行封装,封装后作为一个独立的整…

Flutter仿照微信实现九宫格头像

一、效果图 2、主要代码 import dart:io; import dart:math;import package:cached_network_image/cached_network_image.dart; import package:flutter/material.dart;class ImageGrid extends StatelessWidget {final List<String> imageUrls; // 假设这是你的图片URL…

山东大学软件学院数据库实验1-9(全部)

目录 前言 实验代码 实验一 1-1 1-2 1-3 1-4 1-5 1-6 实验二 2-1 2-2 2-3 2-4 2-5 2-6 2-7 2-8 2-9 2-10 实验三 3-1 3-2 3-3 3-4 3-5 3-6 3-7 3-8 3-9 3-10 实验四 4-1 4-2 4-3 4-4 4-5 4-6 4-7 4-8 4-9 4-10 实验五 5-1…

代码随想录算法训练营第三十六天 | 1005.K次取反后最大化的数组和、134.加油站、135.分发糖果

目录 1005.K次取反后最大化的数组和 思路 代码 代码 134.加油站 思路 代码 135.分发糖果 思路 代码 1005.K次取反后最大化的数组和 本题简单一些&#xff0c;估计大家不用想着贪心 &#xff0c;用自己直觉也会有思路。 代码随想录 思路 直觉&#xff0c;直接写&…

<学习笔记>从零开始自学Python-之-实用库篇(一)-pyscript

由Anaconda创建的PyScript是一项实验性的但很有前途的新技术&#xff0c;它使python运转时在支撑WebAssembly的浏览器中作为一种脚本言语运用。 每个现代常用的浏览器现在都支撑WebAssembly&#xff0c;这是许多言语&#xff08;如C、C和Rust&#xff09;能够编译的高速运转时…

K8S/ hpa分享

在 Kubernetes 中&#xff0c;HorizontalPodAutoscaler 自动更新工作负载资源 &#xff08;例如 Deployment 或者 StatefulSet&#xff09;&#xff0c; 目的是自动扩缩工作负载以满足需求。 hpa的使用本身还是很简单的 示例如下&#xff1a; 官网示例 apiVersion: apps/v1 k…

基础—SQL—DDL—建表、查表、修改表以及总结

一、DDL—表—创建表与数据类型的设定 &#xff08;1&#xff09;要求 根据需求创建表(设计合理的数据类型、长度) 设计一张员工信息表&#xff0c;要求如下: 1、编号&#xff08;纯数字) 2、员工工号(字符串类型&#xff0c;长度不超过10位) 3、员工姓名&#xff08;字符串类…

设计模式10——装饰模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 装饰模式 是一种行为型模式。…

笔记88:LeetCode_134_加油站

前言&#xff1a; 前言1&#xff1a;这个题的题目条件给的不太严谨&#xff0c;题目描述中说“如果存在解&#xff0c;则保证它是唯一的”&#xff0c;通过我的实践&#xff0c;我发现这句话的意思其实是本题的所有样例只有两种情况&#xff0c;无解/有唯一解&#xff1b;而不可…

【Spring】认识 Spring AOP

认识 Spring AOP 1.什么是 AOP2.AOP 中的概念3.用 AOP 方式管理日志3.1 编写 AOP 日志注解类3.2 编写控制器用于测试 1.什么是 AOP AOP&#xff08;Aspect Oriented Program&#xff0c;面向切面编程&#xff09;把业务功能分为核心、非核心两部分。 核心业务功能&#xff1a…

tcpdump源码分析

进入tcpdump.c&#xff08;函数入口&#xff09;之前&#xff0c;先看一些头文件netdissect.h里定义了一个数据结构struct netdissect_options来描述tcdpump支持的所有参数动作&#xff0c;每一个参数有对应的flag, 在tcpdump 的main 里面&#xff0c; 会根据用户的传入的参数来…

构建高效的在线培训机构CRM应用架构实践

在当今数字化时代&#xff0c;在线培训已成为教育行业的重要趋势之一。为了提供更好的学习体验和管理服务&#xff0c;在线培训机构需要构建高效的CRM&#xff08;Customer Relationship Management&#xff09;应用架构。本文将探讨在线培训机构CRM应用架构的设计与实践。 一、…

力扣周赛398题解

特殊数组Ⅰ 如果数组的每一对相邻元素都是两个奇偶性不同的数字&#xff0c;则该数组被认为是一个 特殊数组 。 Aging 有一个整数数组 nums。如果 nums 是一个 特殊数组 &#xff0c;返回 true&#xff0c;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;nums [1] …

数据结构和算法|排序算法系列(二)|冒泡排序

首先需要你对排序算法的评价维度和一个理想排序算法应该是什么样的有一个基本的认知&#xff1a; 《Hello算法之排序算法》 主要内容来自&#xff1a;Hello算法11.3 冒泡排序 我觉得冒泡排序非常有意思&#xff0c;也非常简单&#xff0c;就是不停地交换相邻的元素即可&#…