2024年11月

引言

随着技术的不断进步,微软的.NET 框架在每次迭代中都带来了令人惊喜的新特性。在.NET 9 版本中,一个特别引人注目的亮点是 AOT( Ahead-of-Time)支持,它允许开发人员将应用程序在编译阶段就优化为能够在老旧的 Windows 系统上运行,包括 Windows 7 和甚至 Windows XP。这不仅提升了性能,也为那些依然依赖这些老平台的企业和个人开发者提供了新的可能性。

小知识普及:

  1. NET 9 AOT 简介

.NET 9 的 AOT 编译器通过静态编译,将.NET 应用程序转换为可以直接在目标机器上执行的可执行文件,消除了在运行时的 JIT(Just-In-Time)编译所需的时间和资源。这对于对性能要求高且需要支持旧版系统的场景具有显著优势。

  1. 支持 Windows 7 与 Windows XP 的背景

尽管 Windows 7 和 XP 已经不再是主流操作系统,但它们在某些特定领域,如企业遗留系统、嵌入式设备或者资源受限的环境中仍有广泛应用。.NET 9 的 AOT 编译这一扩展,旨在满足这些场景的兼容性和性能需求。

  1. 如何实现
  • 编译过程优化
    :NET 9 在 AOT 编译时,对代码进行了更为细致的优化,使得生成的可执行文件更小,启动速度更快。
  • 向下兼容性
    :通过精心设计的编译策略,确保了对 Win7 及 XP API 的兼容性,使代码能够无缝运行。
  • 安全性考量
    :虽然支持老旧系统,但.NET 9 依然注重安全,提供了一定程度的保护机制以抵御潜在的风险。
  1. 实例应用与优势
  • 性能提升
    :AOT 编译后的程序通常比 JIT 执行的程序更快,尤其对于 CPU 密集型任务。
  • 部署简易
    :无需用户安装.NET 运行时,简化了部署流程。
  • 维护成本降低
    :对于依赖老旧系统的企业,避免了频繁升级运行时的困扰。

本文只在分享网友及站长实践的一个成果,如有更多发现,欢迎投稿或给本文PR。

Windows 7 支持

下图是网友编译的 Avalonia UI 跨平台项目在 Win 7 非 SP1 环境运行效果截图:

如上图,左侧是程序运行界面,右侧是操作系统版本。

为了便于读者代码拷贝,参考配置贴出如下:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>WinExe</OutputType>
		<TargetFramework>net9.0-windows</TargetFramework>
		<Nullable>enable</Nullable>
		<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
		<ApplicationManifest>app.manifest</ApplicationManifest>
		<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
		<PublishAot>true</PublishAot>
	</PropertyGroup>
	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
		<InvariantGlobalization>true</InvariantGlobalization>
        <!--支持在Windows XP或更高版本的Windows操作系统上运行,XP下尝试Ava失败-->
		<WindowsSupportedOSPlatformVersion>5.1</WindowsSupportedOSPlatformVersion>
		<RuntimeIdentifier>win-x64</RuntimeIdentifier>
		<TargetPlatformMinVersion>5.1</TargetPlatformMinVersion>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="VC-LTL" Version="5.1.1-Beta3" />
	</ItemGroup>
	<ItemGroup>
		<PackageReference Include="Avalonia" Version="11.1.1" />
		<PackageReference Include="Avalonia.Desktop" Version="11.1.1" />
		<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.1" />
		<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.1" />
		<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
		<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.1" />
		<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.1" />
	</ItemGroup>
</Project>

上面关键配置说明:

  1. <PublishAot>true</PublishAot>

该开关用于支持AOT编译发布

  1. <WindowsSupportedOSPlatformVersion>5.1</WindowsSupportedOSPlatformVersion>

支持在Windows XP或更高版本的Windows操作系统上运行

  1. VC-LTL

VC-LTL是一个基于微软VC修改的开源运行时,有效减少应用程序体积并摆脱微软运行时DLL,比如msvcr120.dll、api-ms-win-crt-time-l1-1-0.dll等依赖。

Win7及以上版本,可能AOT就能正常运行(不需要安装.NET运行时)。但也有可能在目标系统运行失败,可添加该库尝试重新AOT编译。详细原理参考该仓库:
https://github.com/Chuyu-Team/VC-LTL

经站长实测:Windows7可能还需要添加YY-Thunks包引用:

<PackageReference Include="YY-Thunks" Version="1.1.4-Beta3" />

关于YY-Thunks:
链接
,说明:

众所周知,从 Windows 的每次更新又会新增大量 API,这使得兼容不同版本的 Windows 需要花费很大精力。导致现在大量开源项目已经不再兼容一些早期的 Windows 版本,比如 Windows XP RTM。

难道就没有一种快速高效的方案解决无法定位程序输入点的问题吗?

YY-Thunks(鸭船),存在的目的就是抹平不同系统的差异,编译时单纯添加一个 obj 即可自动解决这些兼容性问题。让你兼容旧版本 Windows 更轻松!

经测试,Winform 可以.NET 9 x86 AOT发布后运行,效果截图如下:

Winform 工程配置如下:

可拷贝配置如下:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>WinExe</OutputType>
		<TargetFramework>net9.0-windows</TargetFramework>
		<Nullable>enable</Nullable>
		<UseWindowsForms>true</UseWindowsForms>
		<ImplicitUsings>enable</ImplicitUsings>
	</PropertyGroup>
	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
		<InvariantGlobalization>true</InvariantGlobalization>
		<WindowsSupportedOSPlatformVersion>5.1</WindowsSupportedOSPlatformVersion>
		<RuntimeIdentifier>win-x64</RuntimeIdentifier>
		<TargetPlatformMinVersion>5.1</TargetPlatformMinVersion>
		<PublishAot>true</PublishAot>
		<_SuppressWinFormsTrimError>true</_SuppressWinFormsTrimError>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="VC-LTL" Version="5.1.1-Beta3" />
		<PackageReference Include="WinFormsComInterop" Version="0.5.0" />
	</ItemGroup>
</Project>

入口再加一句代码
ComWrappers.RegisterForMarshalling(WinFormsComInterop.WinFormsComWrappers.Instance);

using System.Runtime.InteropServices;

namespace WinFormsAotDemo;

internal static class Program
{
    /// <summary>
    ///  The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        // To customize application configuration such as set high DPI settings or default font,
        // see https://aka.ms/applicationconfiguration.

        ComWrappers.RegisterForMarshalling(WinFormsComInterop.WinFormsComWrappers.Instance);

        ApplicationConfiguration.Initialize();
        Application.Run(new Form1());
    }
}

Windows XP 支持

目前测试可运行控制台程序:

网友得出结论:

XP 需要链接 YY-Thunks,参考链接:
https://github.com/Chuyu-Team/YY-Thunks(前面有提及,Win7如果失败也可以添加该包引用尝试)

大家可关注 YY-Thunks 这个 ISSUE:
https://github.com/Chuyu-Team/YY-Thunks/issues/66

控制台支持 XP 的工程配置如下:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net9.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
	</PropertyGroup>
	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
		<InvariantGlobalization>true</InvariantGlobalization>
		<WindowsSupportedOSPlatformVersion>5.1</WindowsSupportedOSPlatformVersion>
		<SupportWinXP>true</SupportWinXP>
		<PublishAot>true</PublishAot>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="VC-LTL" Version="5.1.1-Beta3" />
	</ItemGroup>
</Project>

网友心得:

有待加强的部分

经测试Prism框架使用会报错:

使用HttpClient也会出错:

2024-08-02

通过阅读开源Avalonia主题库 [Semi.Avalonia](
irihitech/Semi.Avalonia: Avalonia theme inspired by Semi Design (github.com)
) 的源码及作者
Rabbitism
兔佬的PR已经解决Prism问题的,其它库问题使用方法应该类似,修改如下:

主工程添加Roots.xml,内容如下:

<linker>
    <assembly fullname="CodeWF.Toolbox.Desktop" preserve="All"/>
    <assembly fullname="Ursa.PrismExtension" preserve="All" />
    <assembly fullname="Prism" preserve="All" />
    <assembly fullname="DryIoc" preserve="All" />
    <assembly fullname="Prism.Avalonia" preserve="All"/>
    <assembly fullname="Prism.DryIoc.Avalonia" preserve="All"/>
    <assembly fullname="CodeWF.Toolbox" preserve="All" />
</linker>

主工程添加该XML配置:

<ItemGroup>
    <TrimmerRootDescriptor Include="Roots.xml" />
</ItemGroup>

HttpClient也是类似的处理方法,这里不赘述,需要你进行更多尝试。

每个公司的不同项目都是极其不同、复杂的,实际发布还需要不断测试,为了支持Windows7、Windows XP可能不得不做出使用库替换、部分API使用取舍等操作,欢迎读者将使用过程中的心得体会进行分享。

结语

.NET 9 的 AOT 支持无疑拓宽了.NET 生态的应用范围,为那些需要在老旧平台上运行高性能应用的开发者提供了强大的工具。随着技术的发展,我们期待未来更多的.NET 版本能够进一步打破界限,让编程变得更加灵活和高效。

感谢网友
GSD

M$達
分享的这个好消息,大石头这篇文章《各版本操作系统对.NET 支持情况》推荐大家阅读:
https://newlifex.com/tech/os_net

参考AOT项目:
https://github.com/dotnet9/CodeWF.Toolbox

技术交流

软件开发技术交流添加 QQ 群:771992300

或扫站长微信(
codewf
,备注
加群
)加入微信技术交流群:

C# 异步编程中
await
实现原理详解

在C#中,
async

await
关键字用于编写异步代码。本文将详细介绍
await
的实现原理,包括状态机的生成、回调函数的注册和触发等关键步骤。

1. 异步方法的基本概念

在C#中,
async
关键字标记一个方法为异步方法,而
await
关键字用于等待一个异步操作完成。异步方法可以提高程序的响应性和性能,特别是在处理I/O操作和网络请求时。

2. 示例异步方法

我们以一个简单的异步方法为例,来详细解释
await
的实现原理。

public class Example
{
    public async Task<int> CalculateAsync()
    {
        int a = await Task.Run(() => 10);
        int b = await Task.Run(() => 20);
        return a + b;
    }
}

3. 编译器生成的状态机

编译器会为每个异步方法生成一个状态机。状态机是一个结构体,包含了异步方法的所有局部变量和状态信息。

编译器生成的状态机类

public class Example
{
    public Task<int> CalculateAsync()
    {
        <CalculateAsync>d__0 stateMachine = new <CalculateAsync>d__0();
        stateMachine.<>4__this = this;
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }

    [StructLayout(LayoutKind.Auto)]
    [AsyncMethodBuilder(typeof(AsyncTaskMethodBuilder<int>))]
    private struct <CalculateAsync>d__0 : IAsyncStateMachine
    {
        public int <>1__state;
        public AsyncTaskMethodBuilder<int> <>t__builder;
        public Example <>4__this;
        public int <a>5__1;
        public TaskAwaiter<int> <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            try
            {
                TaskAwaiter<int> awaiter;
                switch (num)
                {
                    case 0:
                        goto TR_0000;
                    case 1:
                        <>1__state = -1;
                        awaiter = <>u__1;
                        <>u__1 = default(TaskAwaiter<int>);
                        goto TR_0001;
                    case 2:
                        <>1__state = -1;
                        break;
                    default:
                        <>1__state = 0;
                        awaiter = Task.Run<int>(() => 10).GetAwaiter();
                        if (!awaiter.IsCompleted)
                        {
                            num = (<>1__state = 0);
                            <>u__1 = awaiter;
                            <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                            return;
                        }
                        goto TR_0000;
                }
                TR_0000:
                <a>5__1 = awaiter.GetResult();
                awaiter = Task.Run<int>(() => 20).GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    num = (<>1__state = 1);
                    <>u__1 = awaiter;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                    return;
                }
                TR_0001:
                int b = awaiter.GetResult();
                int result = <a>5__1 + b;
                <>1__state = -2;
                <>t__builder.SetResult(result);
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
            }
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }
    }
}

4. 实现流程详解

初始化状态机


CalculateAsync
方法中,创建状态机实例
<CalculateAsync>d__0

<CalculateAsync>d__0 stateMachine = new <CalculateAsync>d__0();
stateMachine.<>4__this = this;
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
  • <>4__this
    :指向当前实例,即
    Example
    类的实例。
  • <>t__builder
    :创建
    AsyncTaskMethodBuilder<int>
    实例,用于管理任务的生命周期。
  • <>1__state
    :初始化状态为
    -1
    ,表示方法尚未开始执行。

开始执行

调用
Start
方法开始执行异步方法。
Start
方法会调用状态机的
MoveNext
方法。

stateMachine.<>t__builder.Start(ref stateMachine);

执行方法体


MoveNext
方法中,根据当前状态
<>1__state
执行相应的代码。

private void MoveNext()
{
    int num = <>1__state;
    try
    {
        TaskAwaiter<int> awaiter;
        switch (num)
        {
            // 处理不同的状态
        }
    }
    catch (Exception exception)
    {
        <>1__state = -2;
        <>t__builder.SetException(exception);
    }
}

遇到
await

遇到第一个
await
关键字时,调用
Task.Run(() => 10).GetAwaiter()
获取
Awaiter
对象。

awaiter = Task.Run<int>(() => 10).GetAwaiter();
  • 检查
    awaiter.IsCompleted
    ,如果任务已经完成,直接调用
    awaiter.GetResult()
    获取结果。
  • 如果任务未完成,记录当前状态
    <>1__state
    ,保存
    awaiter
    对象,并调用
    <>t__builder.AwaitUnsafeOnCompleted
    注册回调。
if (!awaiter.IsCompleted)
{
    num = (<>1__state = 0);
    <>u__1 = awaiter;
    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
    return;
}

注册回调

AwaitUnsafeOnCompleted
方法会注册一个回调,当任务完成时,回调会被触发。

public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
    where TAwaiter : ICriticalNotifyCompletion
    where TStateMachine : IAsyncStateMachine
{
    awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
}
  • awaiter.UnsafeOnCompleted
    方法注册一个回调函数,该回调函数会在任务完成时被触发。
  • stateMachine.MoveNext
    是一个委托,指向状态机的
    MoveNext
    方法。

任务完成

当任务完成时,回调会被触发,重新调用
MoveNext
方法,恢复异步方法的执行。

public void OnCompleted(Action continuation)
{
    task.ContinueWith(_ => continuation(), TaskScheduler.Default);
}

继续执行

从上次暂停的地方继续执行方法体。

TR_0000:
<a>5__1 = awaiter.GetResult();
awaiter = Task.Run<int>(() => 20).GetAwaiter();
if (!awaiter.IsCompleted)
{
    num = (<>1__state = 1);
    <>u__1 = awaiter;
    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
    return;
}
  • 遇到第二个
    await
    关键字时,重复上述步骤。

方法完成

当所有异步操作完成并计算出结果后,设置状态
<>1__state

-2
,表示方法已经完成。

int b = awaiter.GetResult();
int result = <a>5__1 + b;
<>1__state = -2;
<>t__builder.SetResult(result);
  • 调用
    <>t__builder.SetResult
    设置任务的结果。
  • 如果在执行过程中抛出异常,捕获异常并调用
    <>t__builder.SetException
    设置任务的异常。
catch (Exception exception)
{
    <>1__state = -2;
    <>t__builder.SetException(exception);
}

5. 深入理解
AsyncTaskMethodBuilder

AsyncTaskMethodBuilder
是一个辅助类,用于构建和管理异步方法的任务。它提供了以下方法:

  • Create
    :创建一个新的
    AsyncTaskMethodBuilder
    实例。
  • Start
    :开始执行异步方法,调用状态机的
    MoveNext
    方法。
  • AwaitUnsafeOnCompleted
    :注册回调函数,当任务完成时触发回调。
  • SetResult
    :设置任务的结果。
  • SetException
    :设置任务的异常。

AsyncTaskMethodBuilder
的内部实现

AsyncTaskMethodBuilder
内部维护了一个
Task
对象,用于表示异步操作的结果。当异步方法完成时,
SetResult
方法会设置任务的结果,
SetException
方法会设置任务的异常。

public struct AsyncTaskMethodBuilder<TResult>
{
    private Task<TResult> task;

    public static AsyncTaskMethodBuilder<TResult> Create()
    {
        return new AsyncTaskMethodBuilder<TResult>(new Task<TResult>());
    }

    private AsyncTaskMethodBuilder(Task<TResult> task)
    {
        this.task = task;
    }

    public void Start<TStateMachine>(ref TStateMachine stateMachine)
        where TStateMachine : IAsyncStateMachine
    {
        stateMachine.MoveNext();
    }

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine
    {
        awaiter.OnCompleted(stateMachine.MoveNext);
    }

    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine
    {
        awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
    }

    public void SetResult(TResult result)
    {
        task.SetResult(result);
    }

    public void SetException(Exception exception)
    {
        task.SetException(exception);
    }

    public Task<TResult> Task => task;
}

6. 异步方法的生命周期

异步方法的生命周期可以分为以下几个阶段:

  1. 初始化
    :创建状态机实例,初始化状态和任务构建器。
  2. 开始执行
    :调用
    Start
    方法开始执行异步方法。
  3. 执行方法体
    :在
    MoveNext
    方法中,根据当前状态执行相应的代码。
  4. 遇到
    await

    :检查任务是否完成,如果未完成则注册回调并暂停方法执行。
  5. 任务完成
    :回调被触发,重新调用
    MoveNext
    方法,恢复异步方法的执行。
  6. 方法完成
    :所有异步操作完成,设置任务的结果或异常。

7. 异步方法的优势

使用
async

await
编写的异步方法有以下优势:

  • 提高响应性
    :异步方法不会阻塞主线程,应用程序可以继续响应用户的输入和其他事件。
  • 提高性能
    :异步方法可以并发执行多个任务,充分利用系统资源。
  • 简化代码
    :异步方法的代码结构类似于同步方法,易于理解和维护。

8. 异步方法的注意事项

尽管
async

await
提供了许多优势,但在使用时也需要注意以下几点:

  • 避免
    async void


    async void
    方法主要用于事件处理程序,其他情况下应避免使用,因为它无法被等待,并且异常处理较为困难。
  • 异常处理
    :异步方法中的异常会被包装在
    AggregateException
    中,需要特殊处理。
  • 资源管理
    :异步方法中使用
    using
    语句时,需要注意
    Dispose
    方法的调用时机。

9. 完整的流程图

为了更好地理解这个过程,可以用流程图来展示:

总结

通过上述详细的解释和示例代码,我们可以总结出以下几点:

  1. 异步方法的基本概念

    async

    await
    关键字用于编写异步代码。
  2. 状态机的生成
    :编译器为每个异步方法生成一个状态机,包含所有局部变量和状态信息。
  3. MoveNext
    方法的执行


    MoveNext
    方法是状态机的核心,负责管理和执行异步操作。
  4. 回调函数的注册和触发

    • 当遇到
      await
      关键字时,编译器会生成代码来检查任务是否已经完成。
    • 如果任务未完成,注册回调并暂停方法执行。
    • 当任务完成时,回调函数会被触发,重新调用状态机的
      MoveNext
      方法,从而恢复异步方法的执行。
  5. AwaitUnsafeOnCompleted
    方法的作用

    :在任务完成时注册一个回调函数,回调函数会在任务完成后被触发,从而恢复异步方法的执行。

希望这些解释能帮助你更好地理解
await
实现原理。如果你还有任何疑问,请随时提问!
详情请看:
https://www.cnblogs.com/Bob-luo/p/18518463

希望这篇文章对你有所帮助!如果你有任何进一步的问题或需要更多的细节,请告诉我。

来源:晓飞的算法工程笔记 公众号,转载请注明出处

论文: Open-World Dynamic Prompt and Continual Visual Representation Learning

创新点

  • 在开放世界中建立了一种新的持续视觉表征学习的实用设置。
  • 提出了一种简单而强大的方法,动态提示与表征学习器(
    DPaRL

    Dynamic Prompt and Representation Learner
    ),该方法在有效更新区分性表征主干网络的同时动态生成提示。这一增强提高了在测试时对未见开放世界类别的泛化能力。
  • 在所提议的实用设置中,
    DPaRL
    表现超越了最先进的持续学习方法,无论是无回放方法还是基于回放的方法。

内容概述


开放世界本质上是动态的,特点是不断演变的概念和分布。在这种动态开放世界环境中,持续学习(
CL
)带来了一个重大挑战,即如何有效地泛化到未见的测试时类。为了解决这一挑战,论文提出了一种新的、针对开放世界视觉表示学习的实际
CL
设置。在这一设置中,后续数据流系统性地引入与先前训练阶段中所见类不相交的新类,同时与未见的测试类保持区别。

为此,论文提出了动态提示和表示学习器(
DPaRL
),这是一种简单但有效的基于提示的持续学习(
PCL
)方法。
DPaRL
学习生成用于推理的动态提示,而不是依赖于之前
PCL
方法中的静态提示池。此外,
DPaRL
在每个训练阶段共同学习动态提示生成和区分性表示,而以前的
PCL
方法仅在整个过程中细化提示学习。

实验结果表明,方法的优越性,在公认的开放世界图像检索基准上,
DPaRL

Recall@1
性能上平均提高了
4.7%
,超越了最新的先进方法。

Dynamic Prompt and Representation Learner (DPaRL)


封闭世界与开发世界设置

视觉表示学习的两个基本设置是封闭世界和开放世界范式。在封闭世界设置中(如图 (a/b) 所示),持续训练和测试数据的类别是完全相同的。而开放世界设置(如图 (c/d) 所示)中,持续训练和测试类别完全不同,因此需要模型学习能对未见过的概念进行概括的表示。

基于提示的持续学习

基于提示的持续学习(
PCL
)方法使用预训练的视觉变换器(
ViT
)作为封闭世界图像分类的区分性主干,如图 (a) 所示。这些方法创建了一个包含多个提示
token
的提示池,在训练期间仅更新该池中的可学习参数。在推理时,学习到的提示池是静态的,
PCL
方法从该池中选择
tokens
,输入到多个
ViT
主干层进行预测。

论文的方法也采用了这种
PCL
范式,但在训练和测试类别不相交的开放世界设置中,现有的静态
PCL
提示池设计存在局限性,即测试类别内部和外部的距离分布之间的分离有限。

为此,论文引入一个动态提示生成(
DPG
)网络替代静态提示池,通过联合动态提示和表示学习范式,增强了区分性表示主干模型的能力,更有效地对开放世界概念进行概括。

动态提示生成(
DPG
)网络

  • 动态阶段
    token

为了保留动态提示生成(
DPG
)过程中每个持续学习阶段的信息,引入了阶段
token
\(S\)
。在训练阶段
\(t\)
,训练一个阶段
token
\(S_{t}\)
,同时以先进先出(
FIFO
)队列的方式冻结之前的阶段
token
\(S_{t - (q - 1)} \sim S_{t-1}\)
,其中
\(q\)
是最大队列大小,设置为
5
。这确保了来自之前阶段的知识保持不变,同时阶段
token
的总数受到队列大小的限制,而不会随着阶段数的增加而线性扩展。随后,在
DPG
网络中,通过自注意力模块进行阶段间
token
与实例图像
token
之间信息的融合。

  • 映射函数

DPG
生成一个 [
CLS
]
token
,以动态获取任务特定的高级信息。然而,它的大小与主干模型所需的提示
tokens
不同。为了解决这个问题,需要引入一个映射函数来连接 [
CLS
]
token
与提示
tokens
。一种直接的方法是使用单个线性层进行维度转换。然而,这种方法会引入过多的额外参数,导致过度参数化,也可能导致高度压缩的 [
CLS
]
token
信息出现过拟合。

为了解决这个问题,受到
LoRA
的启发,对权重参数施加约束
\(W=AB^T \in \mathbb{R}^{C_{in} \times C_{out}}\)
,其中
\(A \in \mathbb{R}^{C_{in} \times R}\)

\(B \in \mathbb{R}^{C_{out} \times R}\)
,确保最大秩为
\(R< \min(C_{in}, C_{out})\)
。此外,对该低秩线性映射函数应用了
Dropout

LayerNorm
,这进一步有助于避免过拟合并稳定训练。

在这个专用映射函数和预训练神经网络的帮助下,提示
\(P\)
是从输入图像
\(I\)
和阶段
tokens
\(S_{t, q} := S_{t - (q - 1)} \sim S_{t}\)
动态获得的:

\[\begin{equation}
P = Mapping(DPG\mbox{-}Network([S_{t, q}; \hspace{1mm} I])).
\label{eq:propmt_generation}
\end{equation}
\]


\(P\)
重新调整为大小为
\(N_p \times C \times L\)
,其中
\(N_p\)
是提示的数量,
\(C\)
是通道维度,
\(L\)
表示提示应用于主干模型的层数。遵循之前的
PCL
方法中的提示技术,生成的提示将插入到
ViT
主干中的前
\(L\)
层。第
\(l\)
层的提示
\(P_l \in \mathbb{R}^{N \times C}\)
被划分为
\(\{P_{l,k}, P_{l,v}\}\in \mathbb{R}^{\frac{N}{2} \times C}\)
,作为前缀添加到注意力机制中键和值的输入
token
嵌入中。

最终,多头自注意力可表示为:

\[\begin{equation}
h_i = Attention(X_lW_q^i, \hspace{1mm} [P_{k};X]W_k^i, \hspace{1mm} [P_{v};X]W_v^i).
\end{equation}
\]

  • 联合动态提示与表示学习

与之前冻结主干模型的
PCL
方法不同,
DPaRL
以联合学习动态提示生成和带有鉴别性表示学习的主干模型。这种方法旨在最大化整个管道的能力,以整合旧阶段概念和新概念,从而封装多样的语义,帮助在开放世界中对未见类别和未知领域转移的泛化。

利用参数高效的微调技术,
DPaRL
成功地最大化了准确性性能,同时最小化了灾难性遗忘。需要注意的是,用于提示生成的
DPG
编码器权重(管道的左侧)是冻结的,而可学习的参数包括阶段
tokens
、映射函数和表示损失函数中的权重,以及鉴别性表示主干权重。

主要实验




如果本文对你有帮助,麻烦点个赞或在看呗~
更多内容请关注 微信公众号【晓飞的算法工程笔记】

work-life balance.

当爬取微信里的图片使用的时候会发现,当我们浏览器地址栏
访问微信中的图片URL
是没有问题。但当我们在自己项目中使用
img
标签
src
引入的时候,就会出现,
“此图片来自微信公众平台未经允许不可引用”
的问题。这是因为微信为了防止其他平台引入,特意做了类似防盗链的功能。

前往原文地址查看效果:
张苹果博客

网上找了几个解决方法。

一,在页面header中添加meta标签,设置referrer。

<meta name="referrer" content="never">

该属性禁止了header发送页面相关信息,虽然可以阻止一些攻击以及绕过图片防盗链的效果。确实解决了这个问题。但是会出现其他问题如:影响页面跳转和回溯,第三方统计代码失效(如CNZZ、百度统计),SEO和社交媒体分享影响等。

二,img标签上添加 referrerpolicy="no-referrer"

<img src="微信图片url" referrerPolicy="no-referrer"  />

正常应该能解决,但我的项目第一次打开图片的时候没问题,但当我第二次访问的时候,不知道为什么又出现了“此图片来自xxxxx”的问题。

三,第三方代理

 <img src="https://xxx代理地址/?url=微信图片url"/>

需要自己去找一些第三方的代理,但是如果第三方地址挂了,那么就出问题了。所以这个方法可能不太稳定。

想了想,既然别人的可能不稳定,能不能自己写一个呢?后面自己用python简单实现了一下。

四,Python Flask代理服务器

通过Flask框架和requests库实现图片资源的获取和转发。该方法相对稳定(毕竟是自己弄的),但可能受到服务器性能和网络环境的影响。

1,完整代码
from flask import Flask, Response, request
import requests

app = Flask(__name__)

def fetch_image(image_url):
    try:
        response = requests.get(image_url)
        if response.status_code == 200:
            return response.content
        else:
            return None
    except requests.RequestException as e:
        print(f"Error fetching image: {e}")
        return None

@app.route('/fetchImage', methods = ['GET'])
def fetchImage():
    # 获取图片地址
    image_url = request.args.get('url')
    image_data = fetch_image(image_url)
    if image_data:
        mime_type = 'image/jpeg'  # 这里需要替换为实际的MIME类型
        return Response(image_data, mimetype=mime_type)
    else:
        return "Failed to fetch image", 404



if __name__ == '__main__':
    # 使用WSGI服务器运行应用程序
    from gevent.pywsgi import WSGIServer
    http_server = WSGIServer(('127.0.0.1', 5000), app)
    http_server.serve_forever()

2,测试接口没问题

3,部署到服务器上使用。
   <img src="https://zhangpingguo.com/spider/fetchImage?url=https://mmbiz.qpic.cn/sz_mmbiz_png/3Vm2lcZKj6kibOJxU942w70J8zb6ianMzwPvEwSI5SNOzicM47GN9Bo3MvPAeHJr7pjPRVsjxDLgOYKQ3pR8jcHHA/640?wx_fmt=png&from=appmsg"/>

最后结果正常访问。但是估计是我服务器太廉价了,访问速度忒慢了。不管了,反正能用就行...
如你是PC端访问本文,进入
原文张苹果博客
,鼠标移入上方线条,点击“摸鱼日历”可查看效果。

一.系统环境

本文主要基于Kubernetes1.22.2和Linux操作系统Ubuntu 18.04。

服务器版本 docker软件版本 Kubernetes(k8s)集群版本 CPU架构
Ubuntu 18.04.5 LTS Docker version 20.10.14 v1.22.2 x86_64

Kubernetes集群架构:k8scludes1作为master节点,k8scludes2,k8scludes3作为worker节点。

服务器 操作系统版本 CPU架构 进程 功能描述
k8scludes1/192.168.110.128 Ubuntu 18.04.5 LTS x86_64 docker,kube-apiserver,etcd,kube-scheduler,kube-controller-manager,kubelet,kube-proxy,coredns,calico k8s master节点
k8scludes2/192.168.110.129 Ubuntu 18.04.5 LTS x86_64 docker,kubelet,kube-proxy,calico k8s worker节点
k8scludes3/192.168.110.130 Ubuntu 18.04.5 LTS x86_64 docker,kubelet,kube-proxy,calico k8s worker节点

二.前言

Kubernetes集群的证书对于集群的安全性和稳定性至关重要。然而,随着时间的推移,这些证书会过期,导致集群服务不可用。本文将详细介绍如何使用kubeadm工具为Kubernetes集群续期证书。

给Kubernetes集群证书续期的
前提
是已经有一套可以正常运行的Kubernetes集群,关于Kubernetes(k8s)集群的安装部署,可以查看博客《Ubuntu 安装部署Kubernetes(k8s)集群》
https://www.cnblogs.com/renshengdezheli/p/17632858.html,如果你的操作系统是RHEL的,可以查看博客《Centos7
安装部署Kubernetes(k8s)集群》
https://www.cnblogs.com/renshengdezheli/p/16686769.html。

三.Kubernetes证书过期及续期简介

Kubernetes集群在初始化时,会自动生成一系列证书,包括API服务器证书、CA证书、Kubelet证书等。这些证书通常有1年的有效期。当证书过期后,Kubernetes集群的某些服务可能会受到影响,例如API服务器无法访问。为了解决证书过期的问题,我们可以使用kubeadm工具进行证书续期。

四.使用kubeadm为Kubernetes集群证书续期

4.1 查看k8s集群证书过期时间

现在k8s集群已经不能正常运行了,查询pod报错,可以看到报错信息为:“连接API服务器拒绝”。

root@k8scludes1:~# kubectl get pod -o wie
The connection to the server 192.168.110.128:6443 was refused - did you specify the right host or port?

master节点的/etc/kubernetes/pki/目录下存的是各个组件的证书。

root@k8scludes1:~# ls /etc/kubernetes/pki/
apiserver.crt              apiserver-etcd-client.key  apiserver-kubelet-client.crt  ca.crt  ca.srl  front-proxy-ca.crt  front-proxy-ca.srl      front-proxy-client.key  sa.key
apiserver-etcd-client.crt  apiserver.key              apiserver-kubelet-client.key  ca.key  etcd    front-proxy-ca.key  front-proxy-client.crt  mytok.csv               sa.pub

查看master节点的apiserver证书有效期,可以看到证书在2023年4月16号就过期了,证书已经过期一年多了。

root@k8scludes1:~# openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep Not
            Not Before: Apr 16 14:57:44 2022 GMT
            Not After : Apr 16 14:57:44 2023 GMT            

在master节点查看各个组件的证书过期时间。

root@k8scludes1:~# kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[check-expiration] Error reading configuration from the Cluster. Falling back to default configuration

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Oct 21, 2023 14:25 UTC   9y                                      no      
apiserver                  Oct 21, 2023 14:25 UTC   9y              ca                      no      
apiserver-etcd-client      Oct 21, 2023 14:25 UTC   9y              etcd-ca                 no      
apiserver-kubelet-client   Oct 21, 2023 14:25 UTC   9y              ca                      no      
controller-manager.conf    Oct 21, 2023 14:25 UTC   9y                                      no      
etcd-healthcheck-client    Oct 21, 2023 14:25 UTC   9y              etcd-ca                 no      
etcd-peer                  Oct 21, 2023 14:25 UTC   9y              etcd-ca                 no      
etcd-server                Oct 21, 2023 14:25 UTC   9y              etcd-ca                 no      
front-proxy-client         Oct 21, 2023 14:25 UTC   9y              front-proxy-ca          no      
scheduler.conf             Oct 21, 2023 14:25 UTC   9y                                      no      

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Apr 13, 2023 14:57 UTC   7y              no      
etcd-ca                 Apr 13, 2023 14:57 UTC   7y              no      
front-proxy-ca          Apr 13, 2023 14:57 UTC   7y              no      

查看master节点的kubelet证书过期时间。

root@k8scludes1:~# ls /var/lib/kubelet/pki/
kubelet-client-2022-04-16-22-57-47.pem  kubelet-client-current.pem  kubelet.crt  kubelet.key

root@k8scludes1:~# openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -text  |grep Not
            Not Before: Apr 16 14:57:44 2022 GMT
            Not After : Apr 16 14:57:46 2023 GMT

4.2 为master节点续期证书

在master节点给各个组件续签证书。

root@k8scludes1:~# kubeadm certs renew all
[renew] Reading configuration from the cluster...
[renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[renew] Error reading configuration from the Cluster. Falling back to default configuration

certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed
certificate for the front proxy client renewed
certificate embedded in the kubeconfig file for the scheduler manager to use renewed

Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager, kube-scheduler and etcd, so that they can use the new certificates.

在master节点再次查看各个组件的证书过期时间,可以看到证书续签了一年。

root@k8scludes1:~# kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[check-expiration] Error reading configuration from the Cluster. Falling back to default configuration

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Oct 24, 2025 02:53 UTC   364d                                    no      
apiserver                  Oct 24, 2025 02:53 UTC   364d            ca                      no      
apiserver-etcd-client      Oct 24, 2025 02:53 UTC   364d            etcd-ca                 no      
apiserver-kubelet-client   Oct 24, 2025 02:53 UTC   364d            ca                      no      
controller-manager.conf    Oct 24, 2025 02:53 UTC   364d                                    no      
etcd-healthcheck-client    Oct 24, 2025 02:53 UTC   364d            etcd-ca                 no      
etcd-peer                  Oct 24, 2025 02:53 UTC   364d            etcd-ca                 no      
etcd-server                Oct 24, 2025 02:53 UTC   364d            etcd-ca                 no      
front-proxy-client         Oct 24, 2025 02:53 UTC   364d            front-proxy-ca          no      
scheduler.conf             Oct 24, 2025 02:53 UTC   364d                                    no      

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Apr 13, 2025 14:57 UTC   7y              no      
etcd-ca                 Apr 13, 2025 14:57 UTC   7y              no      
front-proxy-ca          Apr 13, 2025 14:57 UTC   7y              no      

当前kubernetes各个组件所使用的kubecong文件都在/etc/kubernetes/里。

root@k8scludes1:~# ls /etc/kubernetes/
admin.conf  admission-control-config-file  audit  controller-manager.conf  kubelet.conf  manifests  pki  scheduler.conf

文件后缀为conf的都是各个组件所需的kubeconfig文件,但是这些文件里使用的证书都是之前过期的证书,需要把conf文件删除并重新生成。

root@k8scludes1:~# ls /etc/kubernetes/*.conf
/etc/kubernetes/admin.conf  /etc/kubernetes/controller-manager.conf  /etc/kubernetes/kubelet.conf  /etc/kubernetes/scheduler.conf

root@k8scludes1:~# mkdir k8sconf_bak

root@k8scludes1:~# cp /etc/kubernetes/*.conf k8sconf_bak/

root@k8scludes1:~# ls k8sconf_bak/
admin.conf  controller-manager.conf  kubelet.conf  scheduler.conf

root@k8scludes1:~# rm -rf /etc/kubernetes/*.conf

root@k8scludes1:~# ls /etc/kubernetes/
admission-control-config-file  audit  manifests  pki

为k8s的各个组件重新生成kubeconfig文件。

root@k8scludes1:~# kubeadm init --kubernetes-version=v1.22.2  phase kubeconfig all
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file

root@k8scludes1:~# ls /etc/kubernetes/
admin.conf  admission-control-config-file  audit  controller-manager.conf  kubelet.conf  manifests  pki  scheduler.conf

替换管理员所用的kubeconfig文件。

root@k8scludes1:~# ls ~/.kube/config
/root/.kube/config

root@k8scludes1:~# rm -rf ~/.kube/config

root@k8scludes1:~# ls ~/.kube/
cache  config.old-20241023  kubens

root@k8scludes1:~# cp /etc/kubernetes/admin.conf ~/.kube/config

root@k8scludes1:~# ls ~/.kube/config
/root/.kube/config

重启kube-scheduler。

root@k8scludes1:~# docker ps | grep kube-scheduler
47ac8592cf5c   b51ddc1014b0                                        "kube-scheduler --au…"   6 minutes ago   Up 6 minutes             k8s_kube-scheduler_kube-scheduler-k8scludes1_kube-system_f637e8449089a70204a39d176f936bc7_289
6e65a5b16329   registry.aliyuncs.com/google_containers/pause:3.5   "/pause"                 6 minutes ago   Up 6 minutes             k8s_POD_kube-scheduler-k8scludes1_kube-system_f637e8449089a70204a39d176f936bc7_75

root@k8scludes1:~# docker ps | awk '/kube-scheduler /{print $1}'
47ac8592cf5c

root@k8scludes1:~# docker rm -f $(docker ps | awk '/kube-scheduler /{print $1}')
47ac8592cf5c

root@k8scludes1:~# kubectl get pods -n kube-system | grep scheduler
kube-scheduler-k8scludes1                  1/1     Running   289 (2y120d ago)   2y191d

查看master节点的kubelet当前使用的证书,kubelet-client-current.pem软链接到了kubelet-client-2024-10-24-11-08-14.pem,说明现在kubelet使用的是最新的证书。

root@k8scludes1:~# ls /var/lib/kubelet/pki/
kubelet-client-2022-04-16-22-57-47.pem  kubelet-client-2024-10-24-11-05-29.pem  kubelet-client-2024-10-24-11-08-14.pem  kubelet-client-current.pem  kubelet.crt  kubelet.key

root@k8scludes1:~# ls /var/lib/kubelet/pki/kubelet-client-current.pem -l
lrwxrwxrwx 1 root root 59 Oct 24 11:08 /var/lib/kubelet/pki/kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2024-10-24-11-08-14.pem

如果kubelet-client-current.pem软链接到kubelet-client-2022-04-16-22-57-47.pem,说明kubelet使用的是旧的证书,重启kebelet即可。

root@k8scludes1:~# systemctl restart kubelet

root@k8scludes1:~# ls /var/lib/kubelet/pki/
kubelet-client-2022-04-16-22-57-47.pem  kubelet-client-2024-10-24-11-05-29.pem  kubelet-client-2024-10-24-11-08-14.pem  kubelet-client-current.pem  kubelet.crt  kubelet.key

root@k8scludes1:~# ls -l /var/lib/kubelet/pki/kubelet-client-current.pem 
lrwxrwxrwx 1 root root 59 Oct 24 11:08 /var/lib/kubelet/pki/kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2024-10-24-11-08-14.pem

在master节点上查看证书签名请求(简称为CSR),如果CONDITION显示的是Approved,Issued,说明证书签名请求CSR已经被批准,则不需要执行
kubectl certificate approve csr-htp29
,如果CONDITION显示的是Pending,则需要手动批准证书签名请求CSR,语法为:
kubectl certificate approve CSR名

root@k8scludes1:~# kubectl get csr
NAME        AGE   SIGNERNAME                                    REQUESTOR                REQUESTEDDURATION   CONDITION
csr-htp29   12m   kubernetes.io/kube-apiserver-client-kubelet   system:node:k8scludes1   <none>              Approved,Issued

#批准证书签名请求CSR
root@k8scludes1:~# kubectl certificate approve csr-htp29
certificatesigningrequest.certificates.k8s.io/csr-rn8xc approved

在master节点查看kubelet的证书过期时间,到2025年才过期了。

root@k8scludes1:~# openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -text  |grep Not
            Not Before: Oct 24 03:03:14 2024 GMT
            Not After : Oct 24 03:03:14 2025 GMT

查看k8s集群状态,可以发现k8scludes1节点已经正常了,但是两个worker节点还是不正常,原因是两个worker节点的证书还是旧的,需要替换为最新的证书。

root@k8scludes1:~# kubectl get node
NAME         STATUS     ROLES                  AGE      VERSION
k8scludes1   Ready      control-plane,master   2y191d   v1.22.2
k8scludes2   NotReady   <none>                 2y191d   v1.22.2
k8scludes3   NotReady   <none>                 2y191d   v1.22.2

4.3 为worker节点替换最新的证书

k8scludes2节点的kubelet使用的还是旧的证书。

root@k8scludes2:~# ls /var/lib/kubelet/pki/
kubelet-client-2022-04-17-01-59-26.pem  kubelet-client-current.pem  kubelet.crt  kubelet.key

root@k8scludes2:~# openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -text  |grep Not
            Not Before: Apr 16 17:54:26 2022 GMT
            Not After : Apr 16 17:54:26 2023 GMT

在master节点生成k8scludes2节点所需的kubelet.conf文件,把kubelet.conf文件放在/tmp/目录。

root@k8scludes1:~# kubeadm init --kubernetes-version=v1.22.2 phase kubeconfig kubelet --node-name k8scludes2 --kubeconfig-dir /tmp/
[kubeconfig] Writing "kubelet.conf" kubeconfig file
 
root@k8scludes1:~# ls /tmp/
kubelet.conf  systemd-private-3e6f81ffe01748ec8909700ec12195cb-systemd-resolved.service-kzxcft  systemd-private-3e6f81ffe01748ec8909700ec12195cb-systemd-timesyncd.service-Uop8xG  vmware-root_751-4290559920

复制文件到k8scludes2节点的/etc/kubernetes/目录下。

root@k8scludes1:~# scp /tmp/kubelet.conf 192.168.110.129:/etc/kubernetes/
root@192.168.110.129's password: 
kubelet.conf                                                                                                                                                                   100% 5671     1.4MB/s   00:00    

k8scludes2节点重启kubelet。

root@k8scludes2:~# systemctl restart kubelet

再次查看k8scludes2节点的kubelet证书,现在已经是最新的证书了。

root@k8scludes2:~# ls /var/lib/kubelet/pki/
kubelet-client-2022-04-17-01-59-26.pem  kubelet-client-2024-10-24-11-29-31.pem  kubelet-client-2024-10-24-11-29-40.pem  kubelet-client-current.pem  kubelet.crt  kubelet.key

root@k8scludes2:~# ls -l /var/lib/kubelet/pki/kubelet-client-current.pem 
lrwxrwxrwx 1 root root 59 Oct 24 11:29 /var/lib/kubelet/pki/kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2024-10-24-11-29-40.pem

root@k8scludes2:~# openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -text  |grep Not
            Not Before: Oct 24 03:24:40 2024 GMT
            Not After : Oct 24 03:24:40 2025 GMT

k8scludes3节点也是类似的操作。

在master节点生成k8scludes3节点所需的kubelet.conf文件,把kubelet.conf文件放在/tmp/目录。

root@k8scludes1:~# rm -rf /tmp/* ;  ls /tmp/

root@k8scludes1:~# kubeadm init --kubernetes-version=v1.22.2 phase kubeconfig kubelet --node-name k8scludes3 --kubeconfig-dir /tmp/
[kubeconfig] Writing "kubelet.conf" kubeconfig file

复制kubelet.conf到k8scludes3节点的/etc/kubernetes/目录下

root@k8scludes1:~# scp /tmp/kubelet.conf 192.168.110.130:/etc/kubernetes/
root@192.168.110.130's password: 
kubelet.conf                                                                                                                                                                   100% 5671     2.6MB/s   00:00    

重启k8scludes3节点的kubelet,现在kubelet使用的是最新的证书了。

root@k8scludes3:~# systemctl restart kubelet

root@k8scludes3:~# ls /var/lib/kubelet/pki/
kubelet-client-2022-04-17-01-59-29.pem  kubelet-client-2024-10-24-11-34-49.pem  kubelet-client-2024-10-24-11-34-57.pem  kubelet-client-current.pem  kubelet.crt  kubelet.key

root@k8scludes3:~# ls -l /var/lib/kubelet/pki/kubelet-client-current.pem 
lrwxrwxrwx 1 root root 59 Oct 24 11:34 /var/lib/kubelet/pki/kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2024-10-24-11-34-57.pem

root@k8scludes3:~# openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -text  |grep Not
            Not Before: Oct 24 03:29:57 2024 GMT
            Not After : Oct 24 03:29:57 2025 GMT

查看k8s集群状态,现在集群恢复正常了。

root@k8scludes1:~# kubectl get node
NAME         STATUS   ROLES                  AGE      VERSION
k8scludes1   Ready    control-plane,master   2y191d   v1.22.2
k8scludes2   Ready    <none>                 2y191d   v1.22.2
k8scludes3   Ready    <none>                 2y191d   v1.22.2

五.总结

本文介绍了如何使用kubeadm工具为Kubernetes集群续期证书。通过定期检查和续期证书,可以确保Kubernetes集群的安全性和稳定性。

  • 在进行证书续期时,请确保有足够的磁盘空间来存储新的证书文件;
  • 如果你的集群配置了自动化工具或脚本来管理Kubernetes集群,确保这些工具和脚本也更新为处理新的证书;
  • 在生产环境中,建议设置证书到期提醒,以便提前进行维护工作。