Konfig: use ConfigMap para implementar la actualización en caliente de la configuración en línea

Prefacio

El uso de kubernetes para implementar aplicaciones se está volviendo cada vez más popular, y ¿cómo se pueden actualizar en caliente las diversas configuraciones requeridas por los servicios que se ejecutan en kubernetes? ¿Es necesario implementar servicios como zookeeper o etcd en kubernetes? La solución adoptada en este artículo es usar ConfigMap como una solución persistente para la configuración del servicio y usar la capacidad de vigilancia proporcionada por kubernetes para descubrir activamente las actualizaciones de ConfigMap y actualizarlas a la configuración del servicio a tiempo. De esta forma, el personal de operación y mantenimiento solo necesita usar la consola de kubernetes (cli o web) para modificar la configuración del servicio en línea, como modificar el nivel de registro, degradar, ajustar el umbral, etc.

Este artículo hace referencia al código fuente https://github.com/jindezgm/konfig/blob/master/konfig.go

lograr

Konfig es un árbol de configuración implementado usando un ConfigMap de kubernetes. Aunque ConfigMap.Data es del tipo de cadena map [string], konfig realizará un análisis yaml adicional (recursivo) del valor en ConfigMap.Data, siempre que el valor sea "- - \ n "al principio. El propósito de este diseño es hacer que konfig admita la configuración de varios niveles, mientras que solo un nivel de ConfigMap no es compatible en algunos escenarios de uso. Por supuesto, la configuración multinivel también se puede lograr usando un nivel, pero el multinivel se refleja en la clave, como: "abc", "cd", el autor piensa que no es visualmente elegante.

Konfig admite la obtención de la misma configuración en varios tipos, que se pueden convertir a un tipo específico según sea necesario, como se muestra en la siguiente definición de interfaz:

// 所有接口都会返回配置的版本号,即ConfigMap.ResourceVersion,keys是多级的key,当keys为空时表示根,即整个ConfigMap.
type Interface interface {
    // 如果keys已经被注册某种类型(参看下面的RegValue接口),则返回指定类型的值,否则返回原生类型的值。
	Get(keys ...string) (interface{}, int64)
    // 获取布尔型
	GetBool(keys ...string) (bool, int64)
    // 获取64、32位整型、
	GetInt64(keys ...string) (int64, int64)
	GetInt(keys ...string) (int, int64)
	GetInt32(keys ...string) (int32, int64)
    // 获取64、32位浮点型
	GetFloat64(keys ...string) (float64, int64)
	GetFloat32(keys ...string) (float32, int64)
    // 获取字符串
	GetString(keys ...string) (string, int64)
    // 将指定keys下的值注册为一种类型,配合Get()接口使用可以将keys下的值转换为注册的类型返回,其中tag是成员变量的tag名称,比如json
    RegValue(ptr interface{}, tag string, keys ...string) (interface{}, int64)
    // 获取指定类型的value,konfig会将map[string]interface{}转换为value对象,其中tag是成员变量的tag名称,比如json.
    GetValue(ptr interface{}, tag string, keys ...string) int64
    // 将指定的keys下值挂载/卸载到环境变量,
    MountEnv(keys ...string)
	UnmountEnv()
    // 获取版本号
	Revision() int64
}

Cuando konfig obtiene la configuración del tipo especificado, además del tipo primitivo, hace todo lo posible para convertir el tipo primitivo al tipo especificado. Las siguientes son las conversiones de tipo admitidas por konfig:

  • int, int32, int64: admite punto flotante a entero, cadena a entero (cadena-> float64-> intxx), booleano a entero (verdadero: 1, falso: 0)
  • float32, float64: cadena de soporte para flotar
  • bool: admite entero, conversión de punto flotante a booleano (distinto de cero: verdadero, cero: falso), admite conversión de cadena a booleano ("Verdadero" no distingue entre mayúsculas y minúsculas: verdadero, "Falso" no distingue entre mayúsculas y minúsculas: falso)
  • cadena: admite todos los tipos para convertir a cadena y usa fmt.Sprintf ("% v") para devolver

Konfig garantiza la atomicidad de una única interfaz, pero si dos llamadas consecutivas a la interfaz pueden devolver dos versiones diferentes, significa que ConfigMap ha cambiado entre las dos llamadas a la interfaz. Si los parámetros de configuración obtenidos al llamar dos veces a la interfaz tienen altos requisitos de coherencia de la versión, es necesario volver a llamarlos hasta que las versiones de todas las configuraciones sean las mismas. La probabilidad de que esto ocurra es relativamente baja y la mayoría de ellos se pueden resolver volviendo a llamar, porque la frecuencia de actualización de ConfigMap es extremadamente baja. Sin embargo, este método parece un poco feo.Los usuarios pueden definir una estructura para este tipo de configuración y luego obtener toda la configuración a la vez a través de GetValue (). El método común del autor es definir todo el ConfigMap como un tipo y luego leer todas las configuraciones a la vez. Como se muestra en el siguiente código:

import "github.com/jindezgm/konfig"

// 定义自己的配置类型
type MyConfig struct {
    Bool   bool   `json:"bool"`
    Int    int    `json:"int"`
    String string `json:"string"`
}

kfg, _ := konfig.NewWithClientset(...)
var my MyStruct 
var rev int64

// 应用引用配置的功能实现
for {
    // 版本发生更新时重新获取配置
    if r := kfg.Revision(); r > rev {
        // 此处的keys为空,这是将整个ConfigMap.Data映射为MyStruct
        rev = kfg.GetValue(&my, "json")
    }
    // 使用配置
    ...
}

Debido a que la interfaz GetValue () ejecutará un proceso similar a Unmarshal, tiene cierta sobrecarga y es adecuada para escenarios donde la frecuencia de llamada no es alta. Si se requieren llamadas de alta frecuencia, se recomienda aplicar la configuración de caché (como el código anterior) y decidir si llamar a esta interfaz de acuerdo con la revisión. Si la aplicación quiere guardar estas operaciones problemáticas, llama a la interfaz RegValue () para registrar el tipo en konfig, y konfig lo analiza a pedido. Con la interfaz Get (), puede cumplir con el escenario de llamadas de alta frecuencia. Como se muestra en el siguiente código:

// 将"my"下的所有值注册为MyConfig类型
kfg.RegValue(&MyConfig{}, "json", "my")
for {
    // 每次引用直接调用Get,konfig保证一致性、隔离性以及原子性
    value, _ = kfg.Get("my")
    my := value.(*MyConfig)
    ...
}

En algunos escenarios, un punto de función solo necesita hacer referencia a un elemento de configuración, y el usuario puede llamar directamente a la interfaz cada vez que se refiere a él, sin tener que almacenar en caché la configuración en su propio código (cuando la revisión sea grande, lea el configuración y actualización de la caché), porque el rendimiento de lectura de konfig aún está garantizado. Como se muestra en el siguiente código, imprima de acuerdo con la configuración:

for {
    if p, _ := kfg.GetBool("print"); p {
        fmt.Println("Hello world")
    }
}

Por supuesto, si está acostumbrado a obtener la configuración leyendo variables de entorno, y si el contenedor actualiza las variables de entorno y hará que el contenedor se reinicie, puede usar la interfaz MountEnv () para montar la configuración en las variables de entorno, como se muestra en el siguiente código:

// keys为空,将ConfigMap.Data挂载到环境变量. 需要注意的是,MountEnv()的keys下应该只有一级配置,如果是多级,konfig会用fmt.Sprintf("%v")进行格式化
kfg.MountEnv() 
defer kfg.UnmountEnv()
for {
    if strings.ToLower(os.Getenv("print")) == "true" {
        fmt.Println("Hello world")
    }
}

Konfig admite dos métodos de creación: 1. Utilizando Clientset, 2. Utilizando SharedInformerFactory. El primero es adecuado para escenarios en los que la aplicación no necesita acceder a kubernetes y necesita crear un conjunto de clientes separado para konfig. Puede consultar el código de ejemplo oficial para esta parte; el segundo es adecuado para escenarios donde la aplicación necesita acceder a kubernetes. , luego konfig comparte el Informer con la aplicación. En cualquier caso, debe autorizar al pod para que lea ConfigMap.

Entonces, la pregunta es, ¿por qué no colgar el ConfigMap en un archivo? Respuesta: La puntualidad no es buena, porque la actualización de ConfigMap para la actualización del archivo Pod puede demorar varios segundos (parece que están 10 segundos en mi memoria). Si la configuración en línea necesita actualizarse con urgencia (como una degradación), es no es muy útil, pero konfig no tiene este problema.

insuficiente

  1. Actualmente, el análisis recursivo de konfig solo admite el formato yaml, de hecho, json también es fácil de admitir, no se siente necesario;
  2. Konfig no admite devolución de llamada, es decir, después de que se actualice ConfigMap, la parte modificada se volverá a llamar al usuario La solución actual es resolverlo mediante revisión;
  3. Aunque GetValue () puede obtener múltiples configuraciones a la vez, todas las configuraciones deben estar bajo una clave. Si se obtienen múltiples configuraciones de diferentes ramas del árbol de configuración al mismo tiempo, konfig aún no lo admite, y parece que puede serlo. implementado con flag.FlagSet;
  4. MountEnv () no compara la diferencia entre las dos configuraciones y luego elimina la configuración que no está en el nuevo ConfigMap. Simplemente establece el valor en cada ConfigMap a la variable de entorno; y UnmountEnv () no elimina la variable de entorno, simplemente ya no se actualizan las variables de entorno, estas se pueden lograr, pero la necesidad aún no se ve;

Supongo que te gusta

Origin blog.csdn.net/weixin_42663840/article/details/110676693
Recomendado
Clasificación