2024年3月

原文:
Android 桌面小组件使用-Stars-One的杂货小窝

借助公司上的几个项目,算是学习了Android桌面小组件的用法,记下踩坑记录

基本步骤

1.创建小组件布局

这里需要注意的事,小组件布局里不能使用自定义View,只能使用原生的组件,比如说LinearLayout,TextView,连
约束布局都不能使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:orientation="vertical"
    android:padding="16dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tvDate"
            style="@style/textStyle14"
            android:textColor="#313131"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="2023-12-10" />

        <ImageView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/tvTime"
            android:textColor="#313131"
            style="@style/textStyle14"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="12:10" />

    </LinearLayout>

    <LinearLayout
        android:layout_marginTop="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/result_clean"/>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="0dp"
            android:layout_marginStart="9dp"
            android:gravity="center_vertical"
            android:layout_height="match_parent"
            android:layout_weight="1" >
            <TextView
                style="@style/textStyle14"
                android:textColor="#313131"
                android:textStyle="bold"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="125.4MB"/>
            <TextView
                style="@style/textStyle14"
                android:textColor="#313131"
                android:textStyle="bold"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Junk"/>
        </LinearLayout>

        <TextView
            android:layout_gravity="center_vertical"
            android:id="@+id/tvClean"
            android:textColor="#313131"
            style="@style/textStyle14"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Clean" />

    </LinearLayout>

</LinearLayout>

2.创建provider

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
import android.widget.RemoteViews.RemoteView
import ten.jou.recover.R

class CleaningWidget : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        appWidgetIds.forEach {
			//如果小组件布局中使用不支持的组件,这里创建RemoteViews时候,IDE会报红提示!
            val remoteView = RemoteViews(context.packageName, R.layout.widget_layout)
			//绑定数据
			remoteView.setTextViewText(R.id.tv1,"hello world")
            appWidgetManager.updateAppWidget(it, remoteView)
        }

    }
}

AppWidgetProvider本质就是一个广播接收器,所以在清单文件需要声明(见步骤4)

这里先补充下,RemoteViews对于TextView,ImageView等View,有设置文本,字体颜色,图片等相关方法,但并不是所有方法都支持,绑定数据的时候需要注意下小组件是否支持!

3.创建xml属性声明

在xml文件夹里新建widget_info.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:targetCellWidth="4"
    android:targetCellHeight="2"
    android:minWidth="250dp"
    android:minHeight="110dp"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_layout"
    tools:targetApi="s">
</appwidget-provider>

Android12版本以上新增的2个属性,声明组件是4*2大小

  • targetCellWidth
  • targetCellHeight

4.清单文件声明

<receiver
	android:name=".view.CleaningWidget"
	android:enabled="true"
	android:exported="true">
	<intent-filter>
		<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
	</intent-filter>
	<meta-data
		android:name="android.appwidget.provider"
		android:resource="@xml/widget_info" />
</receiver>

5.代码添加小组件

官方说Android12不允许直接通过代码添加小组件,只能让用户手动去桌面拖动添加,但是我手头的三星系统却是支持的(也是Android12),具体还没有细究...

而官方文档上的写的例子如下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
	val context = this@DesktopWidgetActivity

	val appWidgetManager: AppWidgetManager =
		context.getSystemService(AppWidgetManager::class.java)
	val myProvider = ComponentName(context, CleaningWidget::class.java)

	//判断启动器是否支持小组件pin
	val successCallback = if (appWidgetManager.isRequestPinAppWidgetSupported) {
		// Create the PendingIntent object only if your app needs to be notified
		// that the user allowed the widget to be pinned. Note that, if the pinning
		// operation fails, your app isn't notified.
		Intent(context, CleaningWidget::class.java).let { intent ->
			// Configure the intent so that your app's broadcast receiver gets
			// the callback successfully. This callback receives the ID of the
			// newly-pinned widget (EXTRA_APPWIDGET_ID).
			//适配android12的
			val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
				PendingIntent.FLAG_MUTABLE
			} else {
				PendingIntent.FLAG_UPDATE_CURRENT
			}
			PendingIntent.getBroadcast(
				context,
				0,
				intent,
				flags
			)
		}
	} else {
		null
	}

	appWidgetManager.requestPinAppWidget(myProvider, null, successCallback)
}

这里提下,上面的设置flags方法

val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
	PendingIntent.FLAG_MUTABLE
} else {
	PendingIntent.FLAG_UPDATE_CURRENT
}

有个新项目的targetSdk为34(即Android14),如果使用上面的代码会出现下面崩溃错误提示

Targeting U+ (version 34 and above) disallows creating or retrieving a PendingIntent with FLAG_MUTABLE, an implicit Intent within and without FLAG_NO_CREATE and FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT for security reasons. To retrieve an already existing PendingIntent, use FLAG_NO_CREATE, however, to create a new PendingIntent with an implicit Intent use FLAG_IMMUTABLE.

实际上提示已经告诉我们怎么去改代码了,我这里把
PendingIntent.FLAG_MUTABLE
改为
FLAG_IMMUTABLE
就不会出现了上述的崩溃问题

应该是Android14添加的限制:

  • 如果Intent不传数据,必须使用
    PendingIntent.FLAG_IMMUTABLE
  • 如果是需要传递数据,则还是需要使用
    PendingIntent.FLAG_MUTABLE

定时刷新小组件UI

首先,我们得知道,如何主动去更新数据:

val context = it.context
val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
val myProvider = ComponentName(context, CleaningWidget::class.java)
val remoview = CleaningWidget.getRemoteViewTest(context)

//更新某类组件
appWidgetManager.updateAppWidget(myProvider,remoview)
//更新具体某个组件id
appWidgetManager.updateAppWidget(widgetId,remoview)

getRemoteViewTest方法就是创建一个remoteview,然后调用remoteview相关方法设置文本之类的进行数据填充,代码就略过不写了,详见上述基本步骤2

上面的方法我们注意到
updateAppWidget
可以
传不同的参数
,一般我们用的第二个方法,指定更新某个组件

但这里又是需要我们传一个组件id,所以就是在步骤2的时候,我们根据需要需要存储下widgetId比较好,
一般存入数据库,或者使用SharePreference也可

然后,就是对于定时的情况和对应方案:

  1. 如果是间隔多长更新一次,可以使用开一个服务,在服务中开启协程进行
  2. 如果是单纯的时间文本更新,可以使用TextClock组件,比如说 12:21这种
  3. 小组件的xml中默认可以设置定时更新时长,不过最短只能需要15分钟
  4. 可以使用闹钟服务AlarmManager来实现定时,不过此用法需要结合pendingintent和广播接收器使用,最终要在广播接收器里调用更新数据方法
  5. JobScheduler来实现定时更新,似乎受系统省电策略影响,适用于不太精确的定时事件(官方文档上推荐这个)
  6. WorkManager来实现定时更新(实际上算是JobScheduler升级版),似乎受系统省电策略影响,适用于不太精确的定时事件

应该是除了第一种方法,其他都是可以在应用被杀死的情况进行更新小组件UI

小组件播放动画

progressbar实现

帧动画不手动调用
anim.start()
方法是不会播放的,然后在网上看到一篇文章,使用了progressbar来实现,步骤如下:

在drawable文件夹准备帧动画文件

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" android:visible="true">
    <item android:drawable="@drawable/cat_1" android:duration="100" />
    <item android:drawable="@drawable/cat_2" android:duration="100" />
    <item android:drawable="@drawable/cat_3" android:duration="100" />
    <item android:drawable="@drawable/cat_4" android:duration="100" />
</animation-list>
<ProgressBar
	android:indeterminateDrawable="@drawable/cat_animdrawable"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"/>

indeterminateDrawable设置为上面的帧动画文件即可

layoutanim实现

主要是利用viewgroup的初次显示的时候,会展示当前view的添加动画效果,从而实现比较简单的动画效果,如平移,缩放等

可以看实现的敲木鱼一文
Android-桌面小组件RemoteViews播放木鱼动画 - 掘金

使用ViewFlipper

ViewFlipper主要是轮播使用的

里面可放几个元素,之后通过设置autoStart为true,则保证自动轮播

flipInterval属性则是每个元素的间隔时间(帧动画的时间),单位为ms

不过在remoteview中使用的话,缺点就是里面的元素数目只能固定死

否则只能通过定义不同layout文件(如3个元素则是某个layout,4个元素则是某个layout,然后根据选择来创建remoteview)

<ViewFlipper
        android:id="@+id/viewFlipper"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:layout_margin="4dp"
        android:autoStart="true"
        android:flipInterval="800">

        <ImageView
            android:id="@+id/vf_img_1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/peace_talisman_1" />

        <ImageView
            android:id="@+id/vf_img_2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/peace_talisman_2" />
    </ViewFlipper>

补充

获取当前桌面的组件id列表

//获得当前桌面已添加的组件的id列表(可能用户添加了多个)
val context = it.context
val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
val myProvider = ComponentName(context, CleaningWidget::class.java)

val info =appWidgetManager.getAppWidgetIds(myProvider)
toast(info.size.toString())

参考

c#的lamba表达式

之前已经写过一些关于委托还有事件的文章,今天就来介绍一下lambda表达式。
首先定义需要的函数以及委托

{
public delegate void DoNothingDelegate();
public delegate void StudyDelegate(int id, string name);

private void DoNothing()
{
    Console.WriteLine("DoNothing");
}

private void Study(int id , string name)
{
    Console.WriteLine($"{id} {name} 学习 .Net高级班 " );
}
}

在.net farmwork 1.0,会这样写我们的匿名函数


   public void Show()
   {
       {
           //.netframework 1.0的写法
           DoNothingDelegate doNothing = new DoNothingDelegate(DoNothing);
           StudyDelegate study = new StudyDelegate(Study);
       }
   }

在.netframework 2.0,会这样写匿名函数, 增加了一个delegate关键字

 {
     DoNothingDelegate doNothing = new DoNothingDelegate (delegate ()
     {
         Console.WriteLine("DoNothing");
     });
     StudyDelegate study = new StudyDelegate( delegate (int id, string name)
     {
         Console.WriteLine($"{id} {name} 学习 .Net高级班 ");
     });
 }

在.netframework3.0,去掉了delegate关键字了,在参数后增加了一个=> goes to

{
    DoNothingDelegate doNothing = new DoNothingDelegate(() =>
    {
        Console.WriteLine("DoNothing");
    });
    StudyDelegate study = new StudyDelegate((int id, string name) =>
    {
        Console.WriteLine($"{id} {name} 学习 .Net高级班 ");
    });
}

在.netframework3.0后期,我们可以省略参数的信息

 StudyDelegate study = new StudyDelegate((id, name) =>
 {
     Console.WriteLine($"{id} {name} 学习 .Net高级班 ");
 });

如果匿名方法体中只有一行代码,可以省略方法题的大括号

StudyDelegate study = new StudyDelegate((id, name) =>Console.WriteLine($"{id} {name} 学习 .Net高级班 "));

只有一个参数的时候,参数的小括号也可以省略掉。

public delegate void StudyNew(int id);
StudyNew study = id => Console.WriteLine($"{id} 学习 .Net高级班 ");

如果方法返回值?
如果lambda表达式中只有一行代码,且有返回值,可以省略return,

Func<int> retNum= () => 1;

lamba函数的本质是什么?

这里使用ilspy进行反编译来看一下匿名方法的实现是怎么样的

img

本质上来说,其实就是一个方法--匿名方法, 在类里面会生成和lambad 表达式参数和返回值完全匹配的方法.

匿名类

有时候,可以需要创建一个临时的类对象,保存数据,方便使用。
一个普通的类对象


  public class Student
  {
      public int Id { get; set; }
      public int ClassId { get; set; }

      public string Name { get; set; }

      public int Age { get; set; }

      public string Description { get; set; }

      public void Study()
      {
          Console.WriteLine($"{this.Id} {this.Name} 跟着老师学习 .Net开发");

      }

      public void StudyQt()
      {
          Console.WriteLine($"{this.Id} {this.Name} 跟着老师学习C++ Qt");
      }
  }

当创建一个普通的类对象的时候,这样去创建一个类对象。

 Student student = new Student()
 {
     Id = 1,
     ClassId = 2,
     Name = "张三",
     Age = 20,
     Description = "这是一个学生"
 };

现在尝试最原始的方法去创建一个匿名类,

object model = new
{
    Id = 1,
    Name = "小楼一夜听春雨",
    Age = 14,
    Description = "魔刀丁鹏"
};

为什么可以定义一个匿名的对象?

因为C#中所有的对象都继承自Object对象.

当尝试使用.去访问其中的属性就会报错.

C#是强类型语言(编译时决定类型),object是在编译时确定类型,因为Object没有Id等属性,所以无法通过.去访问其中的变量.

因此可以使用下面的方法去访问我们的匿名对象中的属性.

 dynamic model1 = new
 {
     Id = 2,
     Name = "天下第一的剑客",
     Age = 18,
     Description = "神剑山庄谢晓峰"
 };

 Console.WriteLine(model1.Id);
 Console.WriteLine(model1.Age);
 Console.WriteLine(model1.Amy); //报错

这里使用了
dynamic
关键字去避开了编译器的检查,会在运行时检查,运行时决定类型.这个出现乱取的问题,导致程序崩溃.

有什么方法可以正确的取出想访问的属性,又可以避免访问不存在的属性那?
var
关键字

  var model2 = new
  {
      Id = 3,
      Name = "天下第二的剑客",
      Age = 16,
      Description = "不会剑法的阿飞"
  };

  Console.WriteLine(model2.Id);
  Console.WriteLine(model2.Name);
  //Console.WriteLine(model2.Aniu); //报错!无法访问不存在的变量

var类型就是弱类型的变量.

使用的注意事项?

  1. 不能在匿名类里面声明方法,同时在声明匿名类的属性时候,就给定匿名类的属性初始值.
  2. 不能给属性重新赋值.
  3. var声明的变量必须初始化,必须能推算出类型,也不允许作为方法的参数类型.

使用的建议?

  1. var配合匿名类型使用
  2. var偷懒,配合复杂类型时使用。
  3. 在不知道具体什么类型的时候就可以使用var来声明

缺陷

在代码阅读的时候,不是很方便。

建议在大家写代码的时候,尽量明确类型。

扩展方法

为什么需要扩展方法?

  1. 扩展:让功能变得更加强大,让不存在功能存在. ---新增逻辑处理
  2. 已经存在方法,正常调用,扩展的东西不影响已经存在的方法
  3. 如果需求变更,需要支持另外的一个新的功能。

接着上面学生的用例,我们可以追加一些需求.

Student student1 = new Student()
{
    Id = 1,
    ClassId = 2,
    Name = "张三",
    Age = 20,
    Description = "这是一个学生"
};

student1.Study();
student1.StudyQt();

如果要增加一个需求--学习嵌入式---直接增加方法.
传统的方式对原有的类进行结构上的修改.

期望:既可以增加新的功能,历史代码不变.直接增加类,在新的类中去完成.

这里就可以使用扩展方法来完成需求.

 public static class MethodExtension
 {
     public static void StudyEmbedded(this Student student)
     {
         Console.WriteLine($"{student.Id} {student.Name} 跟着老师学习嵌入式开发");
     }
 }

program.cs

student.StudyEmbedded();

可以看到做的操作就是:

  1. 把类变成静态类
  2. 把方法的第一个参数+this修饰

这样就完成了一个扩展方法.静态方法的调用--可以像实例方法一样去调用.

不用修改原有的任何类中的类,可以新增功能;

有哪些场景?

  1. 有新的需求来的时候--扩展方法--保证历史代码功能
  2. 要应用第三方的DLL库(提供的功能不完善,我们自己需要升级下----dll,不能修改原有的代码)扩展方法
  3. 封装帮助类库
  4. asp.net core 中,到处都是扩展方法--框架的设计--最小化设计.提供一个最基本、最最最简单的功能,提供给调用方.这种方式在使用的时候,如果想要增强功能,就可以扩展. 好处:
    1. 尽可能简化代码
    2. 灵活分配,需要就扩展什么.按需扩展,不会有代码冗余.

这里有个问题,我可以给任意类型写扩展方法嘛? 注意:扩展object类型.

 public static string SubObj(this object str, int len = 10)
 {
     if (str is null)
     {
         return string.Empty;
     }

     if (str.ToString().Length <= 10)
     {
         return str.ToString();
     }
     else
     {
         str = $"{str.ToString().Substring(0, len)}....";
         return str.ToString();
     }

 }

program.cs

 object o = "object 类型";
 o.SubObj();
  
 int i = 1;
 i.SubObj();//可以

 string sr = "你好";
 sr.SubObj();


 str.SubGeneric();
 student.SubGeneric(); //隐患

总结:

  1. 扩展的类型具有继承性,扩展父类,所有子类都拥有这个功能;扩展的功能可能不适用一些具体的类型;但是仍然可以调用;可以造成一些类型的功能的污染;----慎用
  2. 不建议扩展object,也不是很建议大家去泛型扩展.

大公司与小公司、有分公司与没分公司、根据公司规模、性质,针对不同的情况采取不同的管理模式、考勤管理也不是一概而论的

网络上各种互联网公司的管理教程、一个公司的落寞从抓考勤开始,一个无能的领导只懂抓考勤等等,被这样的各种管理教程洗脑后,一直对对考勤非常反感,非常羡慕一线互联网公司、世界知名公司的宽松的管理。对考勤的认知也有各种误解,被各种管理视频教程给带偏了,公司强化考勤后各种不适宜,特别是反感要求各种严格打卡,总是忘记打卡、挨各种罚款。


网上其实到处宣传抓考勤的就是无能的领导,要以目标考核为导向的培训课程,其实会有一定的误导大家。大家仅供参考就可以。互联网行业好时,员工待遇好,员工多,不考勤、按目标考核等等都被神化;互联网落寞一些后传统型企业还是需要有与严格的工作纪律,严谨的按时出勤,最多就考虑浮动上下班,例如8:30-9:00来的都不算迟到,可以灵活的5:30-6:00下班,稍微有弹性的工作时间,不是一竿子打死。


考勤为什么要严格? 形成一个比较严谨的工作氛围对公司很重要,是公司持续发展的根基。早上需要协调工作,这个没来哪个没来,无法及时协调工作,工作任务被耽误;早上要找领导请示工作、领导没来,时间久了,事情被拖来拖去,各种懒散的习惯,不及时处理的习惯养成了,领导来了,也可能忘记这个事情了,有些重要的事情没及时处理了。
所以早上大家都及时上班,第一时间把工作相关问题沟通协调处理好,公司的各项任务都能更加顺利执行。长时间积累下来、公司的效率就很高。特别是全国性的大公司,一大早联系这里联系不上,联系那里联系不上,时间久了大家的工作积极性、处理问题的时效性,整个公司的风气都会不一样,效率开始低下。


特别是公司规模庞大,外地也有众多分支机构的,考勤抓得不严格,有吃空饷的,有假冒员工、有长期不出勤的,有严格的考勤后, 就能有效杜绝这些贪腐问题,人脸考勤,按时需要上班下班,请假、早退、迟到、矿工的各种严格得到控制,整个集团公司的人力资源强管控也上一个台阶。
有些离职人员,虚假入职人员能得到及时的处理、分公司管理人员也不敢乱来、总部也对全集团正式员工人员的情况可以有更加严谨的管控,有抓手,有用数据分析的基础,围堵管理漏洞、严格落实公司的各项制度的基本之一。假冒临时工、假冒操作工、假冒员工是分公司相关领导贪污的一个渠道、也可以得到有效监管。

单位里未必都是年轻人、每个行业也未必都是互联网行业一样年轻化,晚上很晚都可以在公司,有些人需要回家处理家务,照顾家里老人,孩子等等等。每个员工都会有年龄大的时候,40岁之前根本不知道什么叫疲倦,什么叫累,身体强壮得牛一样,年过45后,大部分人中午不睡一下、下午就会没精神;中午哪怕睡个短短的午觉,下午人都很精神很多,人性化的公司都需要有正常的午休时间大家也可以放松一下身心。

人生是相对长期战斗的过程,按时休息,按时工作,可以更长期的工作;短时间拼命、冲刺、拼过头也对身体有重大损害损伤,40岁后还未必能长期战斗,因为由于过度劳累身体被透支、吃不消了,用长远的眼光看问题,还是需要劳逸结合。
年轻时,还是精力旺盛、体力充沛;步入中年后更需要脑力、经验来战斗,每个年龄阶段优势都不一样。

1、概述

Node Feature Discovery(NFD)是由Intel创建的项目,能够帮助Kubernetes集群更智能地管理节点资源。它通过检测每个节点的特性能力(例如CPU型号、GPU型号、内存大小等)并将这些能力以标签的形式发送到Kubernetes集群的API服务器(kube-apiserver)。然后,通过kube-apiserver修改节点的标签。这些标签可以帮助调度器(kube-scheduler)更智能地选择最适合特定工作负载的节点来运行Pod。

Github:https://github.com/kubernetes-sigs/node-feature-discovery
Docs:https://kubernetes-sigs.github.io/node-feature-discovery/master/get-started/index.html

2、组件架构

NFD 细分为 NFD-Master 和 NFD-Worker 两个组件:

NFD-Master:是一个负责与 kubernetes API Server 通信的Deployment Pod,它从 NFD-Worker 接收节点特性并相应地修改 Node 资源对象(标签、注解)。

NFD-Worker:是一个负责对 Node 的特性能力进行检测的 Daemon Pod,然后它将信息传递给 NFD-Master,NFD-Worker 应该在每个 Node 上运行。

可以检测发现的硬件特征源(feature sources)清单包括:

  • CPU
  • IOMMU
  • Kernel
  • Memory
  • Network
  • PCI
  • Storage
  • System
  • USB
  • Custom (rule-based custom features)
  • Local (hooks for user-specific features)

3、组件安装

(1)安装前查看集群节点状态

[root@master-10 ~]# kubectl get nodes
NAME                  STATUS   ROLES                         AGE   VERSION
master-10.20.31.105   Ready    control-plane,master,worker   31h   v1.21.5

节点详细信息,主要关注标签、注解。

[root@master-10 ~]# kubectl describe nodes master-10.20.31.105 
Name:               master-10.20.31.105
Roles:              control-plane,master,worker
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=master-10.20.31.105
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/control-plane=
                    node-role.kubernetes.io/master=
                    node-role.kubernetes.io/worker=
                    node.kubernetes.io/exclude-from-external-load-balancers=
Annotations:        flannel.alpha.coreos.com/backend-data: {"VtepMAC":"c6:fb:4b:8a:bb:12"}
                    flannel.alpha.coreos.com/backend-type: vxlan
                    flannel.alpha.coreos.com/kube-subnet-manager: true
                    flannel.alpha.coreos.com/public-ip: 10.20.31.105
                    kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Tue, 12 Mar 2024 21:01:31 -0400
Taints:             <none>
........

(2)组件安装

[root@master-10 opt]# kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref=v0.14.2
namespace/node-feature-discovery created
customresourcedefinition.apiextensions.k8s.io/nodefeaturerules.nfd.k8s-sigs.io created
customresourcedefinition.apiextensions.k8s.io/nodefeatures.nfd.k8s-sigs.io created
serviceaccount/nfd-master created
serviceaccount/nfd-worker created
role.rbac.authorization.k8s.io/nfd-worker created
clusterrole.rbac.authorization.k8s.io/nfd-master created
rolebinding.rbac.authorization.k8s.io/nfd-worker created
clusterrolebinding.rbac.authorization.k8s.io/nfd-master created
configmap/nfd-master-conf created
configmap/nfd-worker-conf created
service/nfd-master created
deployment.apps/nfd-master created
daemonset.apps/nfd-worker created

(3)查看组件状态

[root@master-10 opt]# kubectl get pods -n=node-feature-discovery 
NAME                          READY   STATUS    RESTARTS   AGE
nfd-master-5c4684f5cb-hvjjb   1/1     Running   0          4m11s
nfd-worker-cpwx6              1/1     Running   0          4m11s

(4)查看组件日志

可以看到nfd-worker组件默认每隔一分钟检测一次节点特性。

[root@master-10 ~]# kubectl logs -f -n=node-feature-discovery nfd-worker-rlf5t 
I0314 06:30:32.003264       1 main.go:66] "-server is deprecated, will be removed in a future release along with the deprecated gRPC API"
I0314 06:30:32.003372       1 nfd-worker.go:219] "Node Feature Discovery Worker" version="v0.14.2" nodeName="master-10.20.31.105" namespace="node-feature-discovery"
I0314 06:30:32.003589       1 nfd-worker.go:520] "configuration file parsed" path="/etc/kubernetes/node-feature-discovery/nfd-worker.conf"
I0314 06:30:32.004500       1 nfd-worker.go:552] "configuration successfully updated" configuration={"Core":{"Klog":{},"LabelWhiteList":{},"NoPublish":false,"FeatureSources":["all"],"Sources":null,"LabelSources":["all"],"SleepInterval":{"Duration":60000000000}},"Sources":{"cpu":{"cpuid":{"attributeBlacklist":["BMI1","BMI2","CLMUL","CMOV","CX16","ERMS","F16C","HTT","LZCNT","MMX","MMXEXT","NX","POPCNT","RDRAND","RDSEED","RDTSCP","SGX","SGXLC","SSE","SSE2","SSE3","SSE4","SSE42","SSSE3","TDX_GUEST"]}},"custom":[],"fake":{"labels":{"fakefeature1":"true","fakefeature2":"true","fakefeature3":"true"},"flagFeatures":["flag_1","flag_2","flag_3"],"attributeFeatures":{"attr_1":"true","attr_2":"false","attr_3":"10"},"instanceFeatures":[{"attr_1":"true","attr_2":"false","attr_3":"10","attr_4":"foobar","name":"instance_1"},{"attr_1":"true","attr_2":"true","attr_3":"100","name":"instance_2"},{"name":"instance_3"}]},"kernel":{"KconfigFile":"","configOpts":["NO_HZ","NO_HZ_IDLE","NO_HZ_FULL","PREEMPT"]},"local":{},"pci":{"deviceClassWhitelist":["03","0b40","12"],"deviceLabelFields":["class","vendor"]},"usb":{"deviceClassWhitelist":["0e","ef","fe","ff"],"deviceLabelFields":["class","vendor","device"]}}}
I0314 06:30:32.004796       1 metrics.go:70] "metrics server starting" port=8081
I0314 06:30:32.019135       1 nfd-worker.go:562] "starting feature discovery..."
I0314 06:30:32.019364       1 nfd-worker.go:577] "feature discovery completed"
I0314 06:31:32.021520       1 nfd-worker.go:562] "starting feature discovery..."
I0314 06:31:32.021695       1 nfd-worker.go:577] "feature discovery completed"
I0314 06:32:32.027970       1 nfd-worker.go:562] "starting feature discovery..."
I0314 06:32:32.028141       1 nfd-worker.go:577] "feature discovery completed"

可以看到nfd-master组件启动后默认第一分钟相应地修改 Node 资源对象(标签、注解),之后是每隔一个小时修改一次 Node 资源对象(标签、注解),也就是说如果一个小时以内用户手动误修改node资源特性信息(标签、注解),最多需要一个小时nfd-master组件才自动更正node资源特性信息。

[root@master-10 ~]# kubectl logs -n=node-feature-discovery nfd-master-5c4684f5cb-hvjjb 
I0314 06:23:08.190218       1 nfd-master.go:213] "Node Feature Discovery Master" version="v0.14.2" nodeName="master-10.20.31.105" namespace="node-feature-discovery"
I0314 06:23:08.190356       1 nfd-master.go:1214] "configuration file parsed" path="/etc/kubernetes/node-feature-discovery/nfd-master.conf"
I0314 06:23:08.190912       1 nfd-master.go:1274] "configuration successfully updated" configuration=<
	DenyLabelNs: {}
	EnableTaints: false
	ExtraLabelNs: {}
	Klog: {}
	LabelWhiteList: {}
	LeaderElection:
	  LeaseDuration:
	    Duration: 15000000000
	  RenewDeadline:
	    Duration: 10000000000
	  RetryPeriod:
	    Duration: 2000000000
	NfdApiParallelism: 10
	NoPublish: false
	ResourceLabels: {}
	ResyncPeriod:
	  Duration: 3600000000000
 >
I0314 06:23:08.190928       1 nfd-master.go:1338] "starting the nfd api controller"
I0314 06:23:08.191105       1 node-updater-pool.go:79] "starting the NFD master node updater pool" parallelism=10
I0314 06:23:08.860810       1 metrics.go:115] "metrics server starting" port=8081
I0314 06:23:08.861033       1 component.go:36] [core][Server #1] Server created
I0314 06:23:08.861050       1 nfd-master.go:347] "gRPC server serving" port=8080
I0314 06:23:08.861084       1 component.go:36] [core][Server #1 ListenSocket #2] ListenSocket created
I0314 06:23:09.860886       1 nfd-master.go:694] "will process all nodes in the cluster"
I0314 06:23:09.923362       1 nfd-master.go:1086] "node updated" nodeName="master-10.20.31.105"
I0314 07:23:09.224254       1 nfd-master.go:1086] "node updated" nodeName="master-10.20.31.105"
I0314 08:23:09.081362       1 nfd-master.go:1086] "node updated" nodeName="master-10.20.31.105"

(5)查看节点特性信息

可以看到NFD组件已经把节点特性信息维护到了节点标签、注解上,其中标签前缀默认为 feature.node.kubernetes.io/。

[root@master-10 opt]# kubectl describe node master-10.20.31.105 
Name:               master-10.20.31.105
Roles:              control-plane,master,worker
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    feature.node.kubernetes.io/cpu-cpuid.ADX=true
                    feature.node.kubernetes.io/cpu-cpuid.AESNI=true
                    feature.node.kubernetes.io/cpu-cpuid.AVX=true
                    feature.node.kubernetes.io/cpu-cpuid.AVX2=true
                    feature.node.kubernetes.io/cpu-cpuid.AVX512BW=true
                    feature.node.kubernetes.io/cpu-cpuid.AVX512CD=true
                    feature.node.kubernetes.io/cpu-cpuid.AVX512DQ=true
                    feature.node.kubernetes.io/cpu-cpuid.AVX512F=true
                    feature.node.kubernetes.io/cpu-cpuid.AVX512VL=true
                    feature.node.kubernetes.io/cpu-cpuid.CMPXCHG8=true
                    feature.node.kubernetes.io/cpu-cpuid.FMA3=true
                    feature.node.kubernetes.io/cpu-cpuid.FXSR=true
                    feature.node.kubernetes.io/cpu-cpuid.FXSROPT=true
                    feature.node.kubernetes.io/cpu-cpuid.HLE=true
                    feature.node.kubernetes.io/cpu-cpuid.HYPERVISOR=true
                    feature.node.kubernetes.io/cpu-cpuid.LAHF=true
                    feature.node.kubernetes.io/cpu-cpuid.MOVBE=true
                    feature.node.kubernetes.io/cpu-cpuid.MPX=true
                    feature.node.kubernetes.io/cpu-cpuid.OSXSAVE=true
                    feature.node.kubernetes.io/cpu-cpuid.RTM=true
                    feature.node.kubernetes.io/cpu-cpuid.SYSCALL=true
                    feature.node.kubernetes.io/cpu-cpuid.SYSEE=true
                    feature.node.kubernetes.io/cpu-cpuid.X87=true
                    feature.node.kubernetes.io/cpu-cpuid.XSAVE=true
                    feature.node.kubernetes.io/cpu-cpuid.XSAVEC=true
                    feature.node.kubernetes.io/cpu-cpuid.XSAVEOPT=true
                    feature.node.kubernetes.io/cpu-cpuid.XSAVES=true
                    feature.node.kubernetes.io/cpu-hardware_multithreading=false
                    feature.node.kubernetes.io/cpu-model.family=6
                    feature.node.kubernetes.io/cpu-model.id=85
                    feature.node.kubernetes.io/cpu-model.vendor_id=Intel
                    feature.node.kubernetes.io/kernel-config.NO_HZ=true
                    feature.node.kubernetes.io/kernel-config.NO_HZ_FULL=true
                    feature.node.kubernetes.io/kernel-version.full=3.10.0-1160.105.1.el7.x86_64
                    feature.node.kubernetes.io/kernel-version.major=3
                    feature.node.kubernetes.io/kernel-version.minor=10
                    feature.node.kubernetes.io/kernel-version.revision=0
                    feature.node.kubernetes.io/pci-0300_15ad.present=true
                    feature.node.kubernetes.io/system-os_release.ID=centos
                    feature.node.kubernetes.io/system-os_release.VERSION_ID=7
                    feature.node.kubernetes.io/system-os_release.VERSION_ID.major=7
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=master-10.20.31.105
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/control-plane=
                    node-role.kubernetes.io/master=
                    node-role.kubernetes.io/worker=
                    node.kubernetes.io/exclude-from-external-load-balancers=
Annotations:        flannel.alpha.coreos.com/backend-data: {"VtepMAC":"c6:fb:4b:8a:bb:12"}
                    flannel.alpha.coreos.com/backend-type: vxlan
                    flannel.alpha.coreos.com/kube-subnet-manager: true
                    flannel.alpha.coreos.com/public-ip: 10.20.31.105
                    kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
                    nfd.node.kubernetes.io/feature-labels:
                      cpu-cpuid.ADX,cpu-cpuid.AESNI,cpu-cpuid.AVX,cpu-cpuid.AVX2,cpu-cpuid.AVX512BW,cpu-cpuid.AVX512CD,cpu-cpuid.AVX512DQ,cpu-cpuid.AVX512F,cpu-...
                    nfd.node.kubernetes.io/master.version: v0.14.2
                    nfd.node.kubernetes.io/worker.version: v0.14.2
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Tue, 12 Mar 2024 21:01:31 -0400

4、组件应用场景

Node Feature Discovery(NFD)组件的主要应用场景是在Kubernetes集群中提供更智能的节点调度。以下是一些NFD的常见应用场景:

  1. 智能节点调度:NFD可以帮助Kubernetes调度器更好地了解节点的特性和资源,从而更智能地选择最适合运行特定工作负载的节点。例如,如果某个Pod需要较强的GPU支持,调度器可以利用NFD标签来选择具有适当GPU型号的节点。

  2. 资源约束和优化:通过将节点的特性能力以标签的形式暴露给Kubernetes调度器,集群管理员可以更好地理解和利用集群中节点的资源情况,从而更好地进行资源约束和优化。

  3. 硬件感知的工作负载调度:对于特定的工作负载,可能需要特定类型或配置的硬件。NFD可以使调度器能够更加智能地选择具有适当硬件特性的节点来运行这些工作负载。

  4. 集群扩展性和性能:通过更智能地分配工作负载到节点,NFD可以提高集群的整体性能和效率。它可以帮助避免资源浪费,并确保工作负载能够充分利用可用的硬件资源。

  5. 集群自动化:NFD可以集成到自动化流程中,例如自动化部署或缩放工作负载。通过使用NFD,自动化系统可以更好地了解节点的特性和资源,从而更好地执行相应的操作。

总的来说,Node Feature Discovery(NFD)可以帮助提高Kubernetes集群的智能程度,使其能够更好地适应各种类型的工作负载和节点特性,从而提高集群的性能、可靠性和效率。

5、总结

如果您的 Kubernetes 集群需要根据节点的硬件特性进行智能调度或者对节点的硬件资源进行感知和利用,那么安装 Node Feature Discovery(NFD)是有必要的。然而,如果您的集群中的节点都具有相似的硬件配置,且不需要考虑硬件资源的差异,那么不需要安装 NFD。

PBKDF2算法起源:

PBKDF2(Password-Based Key Derivation Function 2)算法是一种基于密码的密钥派生函数,最初由RSA实验室的密码学家提出,用于从密码中生成密钥。PBKDF2算法的设计目的是增加破解密码的难度,提高密码的安全性。

PBKDF2在线加密 | 一个覆盖广泛主题工具的高效在线平台(amd794.com)

https://amd794.com/pbkdf2

PBKDF2算法实现原理:

  1. 初始化
    :设置迭代次数、盐值和输出密钥长度。
  2. 迭代计算
    :通过多次迭代的哈希计算,生成最终的密钥。
  3. 输出密钥
    :生成的密钥用于加密或验证密码。

PBKDF2算法优缺点:

优点

  • 增加密码破解的难度,提高密码安全性。
  • 支持自定义迭代次数和盐值,灵活性强。
  • 易于实现和使用,广泛应用于密码学领域。

缺点

  • 可能存在暴力破解攻击,需要设置足够的迭代次数。
  • 需要消耗较多的计算资源,影响性能。

PBKDF2算法与其他算法对比:

  • 与MD5算法相比
    :PBKDF2算法更为安全,抗暴力破解性更强。
  • 与bcrypt算法相比
    :PBKDF2算法更为灵活,支持自定义参数。

PBKDF2算法解决问题的技术:

  1. 设置合适的迭代次数,增加破解难度。
  2. 使用随机盐值,提高密码安全性。
  3. 结合其他加密算法,构建更为复杂的密码保护系统。

Python示例:

python
import hashlib
import binascii
import os

password = b'VerySecretPassword'
salt = os.urandom(16)
key = hashlib.pbkdf2_hmac('sha256', password, salt, 100000)
print("Derived Key:", binascii.hexlify(key))

JavaScript示例:

javascript
const crypto = require('crypto');

const password = Buffer.from('VerySecretPassword');
const salt = crypto.randomBytes(16);
const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
console.log("Derived Key:", key.toString('hex'));

总结:

PBKDF2算法作为一种密码保护的重要工具,在密码学领域发挥着关键作用。其基于密码的密钥派生函数设计使得密码更加安全,增加了破解的难度。通过设置合适的迭代次数和随机盐值,可以进一步提高密码的安全性。PBKDF2算法易于实现和使用,广泛应用于密码存储、身份验证等场景。在实际应用中,结合其他加密算法,可以构建更为复杂且安全的密码保护系统。PBKDF2算法是密码学领域中的一颗明珠,为密码安全提供了强大的保障。