写什么呢

前段时间使用 C# 写了个项目,使用 Kubernetes API Server,获取信息以及监控 Kubernetes 资源,然后结合 Neting 做 API 网关。

体验地址 http://neting.whuanle.cn:30080/

账号 admin,密码 admin123

本篇文章主要介绍,如何通过 C# 开发基于 Kuberetes 的应用,实现获取 Kubernets 中各种资源的信息,以及实现 Conroller 的前提知识。而在下一篇中则会讲解如何实现 Conroller 和 Kubernetes Operator。

Kubernetes API Server

kube-apiserver 是 k8s 主要进程之一,apiserver 组件公开了 Kubernetes API (HTTP API),apiserver 是 Kubernetes 控制面的前端,我们可以用 Go、C# 等编程语言写代码,远程调用 Kubernetes,控制集群的运行。apiserver 暴露的 endiont 端口是 6443。

为了控制集群的运行,Kubernetes 官方提供了一个名为 kubectl 的二进制命令行工具,正是 apiserver 提供了接口服务,kubectl 解析用户输入的指令后,向 apiserver 发起 HTTP 请求,再将结果反馈给用户。

kubectl

kubectl 是 Kubernetes 自带的一个非常强大的控制集群的工具,通过命令行操作去管理整个集群。

Kubernetes 有很多可视化面板,例如 Dashboard,其背后也是调用 apiserver 的 API,相当于前端调后端。

总之,我们使用的各种管理集群的工具,其后端都是 apiserver,通过 apiserver,我们还可以定制各种各样的管理集群的工具,例如网格管理工具 istio。腾讯云、阿里云等云平台都提供了在线的 kubernetes 服务,还有控制台可视化操作,也是利用了 apiserver。

你可以参考笔者写的 Kubernetes 电子书,了解更多:https://k8s.whuanle.cn/1.basic/5.k8s.html

简而言之, Kubernetes API Server 是第三方操作 Kubernetes 的入口。

暴露 Kubernetes API Server

首先查看 kube-system 中运行的 Kubernetes 组件,有个 kube-apiserver-master 正在运行。

root@master:~# kubectl get pods -o wide  -n kube-system
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
... ...
kube-apiserver-master 1/1 Running 2 (76d ago) 81d 10.0.0.4 master <none> <none>
... ...

虽然这些组件很重要,但是只会有一个实例,并且以 Pod 形式运行,而不是 Deployment,这些组件只能放在 master 节点运行。

然后查看 admin.conf 文件,可以通过 /etc/kubernetes/admin.conf$HOME/.kube/config 路径查看到。

admin.conf 文件是访问 Kubernetes API Server 的凭证,通过这个文件,我们可以使用编程访问 Kubernetes 的 API 接口。

但是 admin.conf 是很重要的文件,如果是开发环境开发集群,那就随便造,如果是生产环境,请勿使用,可通过角色绑定等方式限制 API 访问授权。

然后把 admin.conf 或 config 文件下载到本地。

你可以使用 kubectl edit pods kube-apiserver-master -n kube-system 命令,查看 Kubernetes API Server 的一些配置信息。

由于 Kubernetes API Server 默认是通过集群内访问的,如果需要远程访问,则需要暴露到集群外(与是否都在内网无关,与是否在集群内有关)。

将 API Server 暴露到集群外:

kubectl expose pod  kube-apiserver-master --type=NodePort --port=6443 -n kube-system

查看节点随机分配的端口:

root@master:~# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-apiserver-master NodePort 10.101.230.138 <none> 6443:32263/TCP 25s

32263 端口是 Kubernetes 自动分配,每个人的都不一样。

然后通过 IP:32263 即可测试访问。

如果你的集群安装了 CoreDNS,那么通过其他节点的 IP,也可以访问到这个服务。

然后将下载的 admin.conf 或者 config 文件(请改名为 admin.conf),修改里面的 server 属性,因为我们此时是通过远程访问的。

连接到 API Server

新建一个 MyKubernetes 控制台项目,然后将 admin.conf 文件复制放到项目中,随项目生成输出。

然后在 Nuget 中搜索 KubernetesClient 包,笔者当前使用的是 7.0.1。

然后在项目中设置环境变量:

这个环境变量本身是 ASP.NET Core 自带的,控制台程序中没有。

下面写一个方法,用于实例化和获取 Kubernetes 客户端:

    private static Kubernetes GetClient()
{
KubernetesClientConfiguration config;
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development")
{
// 通过配置文件
config = KubernetesClientConfiguration.BuildConfigFromConfigFile("./admin.conf");
}
else
{
// 通过默认的 Service Account 访问,必须在 kubernetes 中运行时才能使用
config = KubernetesClientConfiguration.BuildDefaultConfig();
}
return new Kubernetes(config);
}

逻辑很简单,如果是开发环境,则使用 admin.conf 文件访问,如果是非开发环境,则 BuildDefaultConfig() 自动获取访问凭证,此方式只在 Pod 中运行时有效,利用 Service Account 认证。

下面测试一下,获取全部命名空间:

    static async Task Main()
{
var client = GetClient();
var namespaces = await client.ListNamespaceAsync();
foreach (var item in namespaces.Items)
{
Console.WriteLine(item.Metadata.Name);
}
}

好了!你已经会获取 Kubernetes 资源了,打开入门的第一步!秀儿!

客户端小知识

虽然打开了入门的第一步,但是不要急着使用各种 API ,这里我们来了解一下 Kubernetes 各种资源在客户端中的定义,和如何解析结构。

首先,在 Kubernetes Client C# 的代码中,所有 Kubernetes 资源的模型类,都在 k8s.Models 中记录。

如果我们要在 Kubernetes 中,查看一个对象的定义,如 kube-systtem 命名空间的:

kubectl get namespace kube-system -o yaml
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: "2021-11-03T13:57:10Z"
labels:
kubernetes.io/metadata.name: kube-system
name: kube-system
resourceVersion: "33"
uid: f0c1f00d-2ee4-40fb-b772-665ac2a282d7
spec:
finalizers:
- kubernetes
status:
phase: Active

C# 中,模型的结构与其一模一样:

在客户端中,模型的名称以 apiVersion 版本做前缀,并且通过 V1NamespaceList 获取这类对象的列表。

如果要获取某类资源,其接口都是以 List 开头的,如 client.ListNamespaceAsync()client.ListAPIServiceAsync()client.ListPodForAllNamespacesAsync() 等。

看来,学习已经步入正轨了,让我们来实验练习吧!

如何解析一个 Service

这里笔者贴心给读者准备了一些练习,第一个练习是解析一个 Service 的信息出来。

查看前面创建的 Servicie:

kubectl get svc  kube-apiserver-master -n kube-system -o yaml

对应结构如下:

apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2022-01-24T12:51:32Z"
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver-master
namespace: kube-system
resourceVersion: "24215604"
uid: ede0e3df-8ef6-45c6-9a8d-2a2048c6cb12
spec:
clusterIP: 10.101.230.138
clusterIPs:
- 10.101.230.138
externalTrafficPolicy: Cluster
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- nodePort: 32263
port: 6443
protocol: TCP
targetPort: 6443
selector:
component: kube-apiserver
tier: control-plane
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}

我们在 C# 中定义一个这样的模型类:

    public class ServiceInfo
{
/// <summary>
/// SVC 名称
/// </summary>
public string Name { get; set; } = null!; /// <summary>
/// 三种类型之一 <see cref="ServiceType"/>
/// </summary>
public string? ServiceType { get; set; }
/// <summary>
/// 命名空间
/// </summary>
public string Namespace { get; set; } = null!; /// <summary>
/// 有些 Service 没有此选项
/// </summary>
public string ClusterIP { get; set; } = null!; /// <summary>
/// 外网访问 IP
/// </summary>
public string[]? ExternalAddress { get; set; } public IDictionary<string, string>? Labels { get; set; } public IDictionary<string, string>? Selector { get; set; } /// <summary>
/// name,port
/// </summary>
public List<string>? Ports { get; set; } public string[]? Endpoints { get; set; } public DateTime? CreationTime { get; set; } // 关联的 Pod 以及 pod 的 ip
}

下面,指定获取哪个命名空间的 Service 及其关联的 Endpoint 信息。

    static async Task Main()
{
var result = await GetServiceAsync("kube-apiserver-master","kube-system");
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result));
}
public static async Task<ServiceInfo> GetServiceAsync(string svcName, string namespaceName)
{
var client = GetClient();
var service = await client.ReadNamespacedServiceAsync(svcName, namespaceName); // 获取 service 本身的信息
ServiceInfo info = new ServiceInfo
{
Name = service.Metadata.Name,
Namespace = service.Metadata.NamespaceProperty,
ServiceType = service.Spec.Type,
Labels = service.Metadata.Labels,
ClusterIP = service.Spec.ClusterIP,
CreationTime = service.Metadata.CreationTimestamp,
Selector = service.Spec.Selector.ToDictionary(x => x.Key, x => x.Value),
ExternalAddress = service.Spec.ExternalIPs?.ToArray(),
}; // service -> endpoint 的信息
var endpoint = await client.ReadNamespacedEndpointsAsync(svcName, namespaceName);
List<string> address = new List<string>();
foreach (var sub in endpoint.Subsets)
{
foreach (var addr in sub.Addresses)
{
foreach (var port in sub.Ports)
{
address.Add($"{addr.Ip}:{port.Port}/{port.Protocol}");
}
} }
info.Endpoints = address.ToArray();
return info;
}

输出结果如下:

亲,如果你对 Kubernetes 的网络知识不太清楚,请先打开 https://k8s.whuanle.cn/4.network/1.network.html 了解一下呢。

实践 2

我们知道,一个 Service 可以关联多个 Pod,为多个 Pod 提供负载均衡等功能。同时 Service 有 externalIP、clusterIP 等属性,要真正解析出一个 Service 是比较困难的。例如 Service 可以只有端口,没有 IP;也可以只使用 DNS 域名访问;也可以不绑定任何 Pod,可以从 Service A DNS -> Service B IP 间接访问 B;

Service 包含的情况比较多,读者可以参考下面这个图,下面我们通过代码,获取一个 Service 的 IP 和端口信息,然后生成对应的 IP+端口结构。

单纯获取 IP 和 端口是没用的,因为他们是分开的,你获取到的 IP 可能是 Cluter、Node、LoadBalancer 的,有可能只是 DNS 没有 IP,那么你这个端口怎么访问呢?这个时候必须根据一定的规则,解析信息,筛选无效数据,才能得出有用的访问地址。

首先定义一部分枚举和模型:


public enum ServiceType
{
ClusterIP,
NodePort,
LoadBalancer, ExternalName
} /// <summary>
/// Kubernetes Service 和 IP
/// </summary>
public class SvcPort
{ // LoadBalancer -> NodePort -> Port -> Target-Port /// <summary>
/// 127.0.0.1:8080/tcp、127.0.0.1:8080/http
/// </summary>
public string Address { get; set; } = null!; /// <summary>
/// LoadBalancer、NodePort、Cluster
/// </summary>
public string Type { get; set; } = null!; public string IP { get; set; } = null!;
public int Port { get; set; }
}
public class SvcIpPort
{
public List<SvcPort>? LoadBalancers { get; set; }
public List<SvcPort>? NodePorts { get; set; }
public List<SvcPort>? Clusters { get; set; }
public string? ExternalName { get; set; }
}

编写解析代码:

    static async Task Main()
{
var result = await GetSvcIpsAsync("kube-apiserver-master","kube-system");
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result));
} public static async Task<SvcIpPort> GetSvcIpsAsync(string svcName, string namespaceName)
{
var client = GetClient();
var service = await client.ReadNamespacedServiceAsync(svcName, namespaceName); SvcIpPort svc = new SvcIpPort(); // LoadBalancer
if (service.Spec.Type == nameof(ServiceType.LoadBalancer))
{
svc.LoadBalancers = new List<SvcPort>();
var ips = svc.LoadBalancers; // 负载均衡器 IP
var lbIP = service.Spec.LoadBalancerIP;
var ports = service.Spec.Ports.Where(x => x.NodePort != null).ToArray();
foreach (var port in ports)
{
ips.Add(new SvcPort
{
Address = $"{lbIP}:{port.NodePort}/{port.Protocol}",
IP = lbIP,
Port = (int)port.NodePort!,
Type = nameof(ServiceType.LoadBalancer)
});
}
} if (service.Spec.Type == nameof(ServiceType.LoadBalancer) || service.Spec.Type == nameof(ServiceType.NodePort))
{
svc.NodePorts = new List<SvcPort>();
var ips = svc.NodePorts; // 负载均衡器 IP,有些情况可以设置 ClusterIP 为 None;也可以手动设置为 None,只要有公网 IP 就行
var clusterIP = service.Spec.ClusterIP;
var ports = service.Spec.Ports.Where(x => x.NodePort != null).ToArray();
foreach (var port in ports)
{
ips.Add(new SvcPort
{
Address = $"{clusterIP}:{port.NodePort}/{port.Protocol}",
IP = clusterIP,
Port = (int)port.NodePort!,
Type = nameof(ServiceType.NodePort)
});
}
} // 下面这部分代码是正常的,使用 {} 可以隔离部分代码,避免变量重名
// if (service.Spec.Type == nameof(ServiceType.ClusterIP))
// 如果 Service 没有 Cluster IP,可能使用了无头模式,也有可能不想出现 ClusterIP
//if(service.Spec.ClusterIP == "None")
{
svc.Clusters = new List<SvcPort>();
var ips = svc.Clusters;
var clusterIP = service.Spec.ClusterIP; var ports = service.Spec.Ports.ToArray();
foreach (var port in ports)
{
ips.Add(new SvcPort
{
Address = $"{clusterIP}:{port.Port}/{port.Protocol}",
IP = clusterIP,
Port = port.Port,
Type = nameof(ServiceType.ClusterIP)
});
}
} if (!string.IsNullOrEmpty(service.Spec.ExternalName))
{
/* NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp-svcname ExternalName <none> myapp.baidu.com <none> 1m
myapp-svcname -> myapp-svc
访问 myapp-svc.default.svc.cluster.local,变成 myapp.baidu.com
*/
svc.ExternalName = service.Spec.ExternalName;
}
return svc;
}

规则解析比较复杂,这里就不详细讲解,读者如有疑问,可联系笔者讨论。

主要规则:LoadBalancer -> NodePort -> Port -> Target-Port

最终结果如下:

通过这部分代码,可以解析出 Service 在 External Name、LoadBalancer、NodePort、ClusterIP 等情况下可真正访问的地址列表。

实践3 Endpoint 列表

如果对 Endpoint 不太了解,请打开 https://k8s.whuanle.cn/4.network/2.endpoint.html 看一下相关知识。

在 Kubernetes 中,Service 不是直接关联 Pod 的,而是通过 Endpoint 间接代理 Pod。当然除了 Service -> Pod,通过 Endpoint,也可以实现接入集群外的第三方服务。例如数据库集群不在 Kubernetes 集群中,但是想通过 Kubernetes Service 统一访问,则可以利用 Endpoint 进行解耦。这里不多说,读者可以参考 https://k8s.whuanle.cn/4.network/2.endpoint.html

这里这小节中,笔者也将会讲解如何在 Kubernetes 中分页获取资源。

首先定义以下模型:


public class SvcInfoList
{
/// <summary>
/// 分页属性,具有临时有效期,具体由 Kubernetes 确定
/// </summary>
public string? ContinueProperty { get; set; } /// <summary>
/// 预计剩余数量
/// </summary>
public int RemainingItemCount { get; set; } /// <summary>
/// SVC 列表
/// </summary>
public List<SvcInfo> Items { get; set; } = new List<SvcInfo>();
} public class SvcInfo
{
/// <summary>
/// SVC 名称
/// </summary>
public string Name { get; set; } = null!; /// <summary>
/// 三种类型之一 <see cref="ServiceType"/>
/// </summary>
public string? ServiceType { get; set; } /// <summary>
/// 有些 Service 没有 IP,值为 None
/// </summary>
public string ClusterIP { get; set; } = null!; public DateTime? CreationTime { get; set; } public IDictionary<string, string>? Labels { get; set; } public IDictionary<string, string>? Selector { get; set; } /// <summary>
/// name,port
/// </summary>
public List<string> Ports { get; set; } public string[]? Endpoints { get; set; }
}

Kubernetes 中的分页,没有 PageNo、PageSize、Skip、Take 、Limit 这些,并且分页可能只是预计,不一定完全准确。

第一次访问获取对象列表时,不能使用 ContinueProperty 属性。

第一次访问 Kubernets 后,获取 10 条数据,那么 Kubernetes 会返回一个 ContinueProperty 令牌,和剩余数量 RemainingItemCount。

那么我们可以通过 RemainingItemCount 计算大概的分页数字。因为 Kubernetes 是不能直接分页的,而是通过类似游标的东西,记录当前访问的位置,然后继续向下获取对象。ContinueProperty 保存了当前查询游标的令牌,但是这个令牌有效期是几分钟。

解析方法:

    public static async Task<SvcInfoList> GetServicesAsync(string namespaceName,
int pageSize = 1,
string? continueProperty = null)
{
var client = GetClient(); V1ServiceList services;
if (string.IsNullOrEmpty(continueProperty))
{
services = await client.ListNamespacedServiceAsync(namespaceName, limit: pageSize);
}
else
{
try
{
services = await client.ListNamespacedServiceAsync(namespaceName,
continueParameter: continueProperty,
limit: pageSize);
}
catch (Microsoft.Rest.HttpOperationException ex)
{
throw ex;
}
catch
{
throw;
}
} SvcInfoList svcList = new SvcInfoList
{
ContinueProperty = services.Metadata.ContinueProperty,
RemainingItemCount = (int)services.Metadata.RemainingItemCount.GetValueOrDefault(),
Items = new List<SvcInfo>()
}; List<SvcInfo> svcInfos = svcList.Items;
foreach (var item in services.Items)
{
SvcInfo service = new SvcInfo
{
Name = item.Metadata.Name,
ServiceType = item.Spec.Type,
ClusterIP = item.Spec.ClusterIP,
Labels = item.Metadata.Labels,
Selector = item.Spec.Selector,
CreationTime = item.Metadata.CreationTimestamp
};
// 处理端口
if (item.Spec.Type == nameof(ServiceType.LoadBalancer) || item.Spec.Type == nameof(ServiceType.NodePort))
{
service.Ports = new List<string>();
foreach (var port in item.Spec.Ports)
{
service.Ports.Add($"{port.Port}:{port.NodePort}/{port.Protocol}");
}
}
else if (item.Spec.Type == nameof(ServiceType.ClusterIP))
{
service.Ports = new List<string>();
foreach (var port in item.Spec.Ports)
{
service.Ports.Add($"{port.Port}/{port.Protocol}");
}
} var endpoint = await client.ReadNamespacedEndpointsAsync(item.Metadata.Name, namespaceName);
if (endpoint != null && endpoint.Subsets.Count != 0)
{
List<string> address = new List<string>();
foreach (var sub in endpoint.Subsets)
{
if (sub.Addresses == null) continue;
foreach (var addr in sub.Addresses)
{
foreach (var port in sub.Ports)
{
address.Add($"{addr.Ip}:{port.Port}/{port.Protocol}");
}
} }
service.Endpoints = address.ToArray();
}
svcInfos.Add(service);
} return svcList;
}

规则解析比较复杂,这里就不详细讲解,读者如有疑问,可联系笔者讨论。

调用方法:

    static async Task Main()
{
var result = await GetServicesAsync("default", 2);
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result.Items)); if (result.RemainingItemCount != 0)
{
while (result.RemainingItemCount != 0)
{
Console.WriteLine($"剩余 {result.RemainingItemCount} 条数据,{result.RemainingItemCount / 3 + (result.RemainingItemCount % 3 == 0 ? 0 : 1)} 页,按下回车键继续获取!");
Console.ReadKey();
result = await GetServicesAsync("default", 2, result.ContinueProperty);
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result.Items));
}
}
}

上面的实践中,代码较多,建议读者启动后进行调试,一步步调试下来,慢慢检查数据,对比 Kubernetes 中的各种对象,逐渐加深理解。

使用 C# 开发 Kubernetes 组件,获取集群资源信息的更多相关文章

  1. Kubernetes(k8s) docker集群搭建

    原文地址:https://blog.csdn.net/real_myth/article/details/78719244 一.Kubernetes系列之介绍篇   •Kubernetes介绍 1.背 ...

  2. Docker Swarm和Kubernetes在大规模集群中的性能比较

    Contents 这篇文章主要针对Docker Swarm和Kubernetes在大规模部署的条件下的3个问题展开讨论.在大规模部署下,它们的性能如何?它们是否可以被批量操作?需要采取何种措施来支持他 ...

  3. 使用 Sealos 在 3 分钟内快速部署一个生产级别的 Kubernetes 高可用集群

    本文首发于:微信公众号「运维之美」,公众号 ID:Hi-Linux. 「运维之美」是一个有情怀.有态度,专注于 Linux 运维相关技术文章分享的公众号.公众号致力于为广大运维工作者分享各类技术文章和 ...

  4. 部署一套完整的Kubernetes高可用集群(二进制,v1.18版)

    一.前置知识点 1.1 生产环境可部署Kubernetes集群的两种方式 目前生产部署Kubernetes集群主要有两种方式: kubeadm Kubeadm是一个K8s部署工具,提供kubeadm ...

  5. 部署一套完整的Kubernetes高可用集群(二进制,最新版v1.18)下

    七.高可用架构(扩容多Master架构) Kubernetes作为容器集群系统,通过健康检查+重启策略实现了Pod故障自我修复能力,通过调度算法实现将Pod分布式部署,并保持预期副本数,根据Node失 ...

  6. 二进制搭建kubernetes多master集群【四、配置k8s node】

    上一篇我们部署了kubernetes的master集群,参考:二进制搭建kubernetes多master集群[三.配置k8s master及高可用] 本文在以下主机上操作部署k8s node k8s ...

  7. 二进制搭建kubernetes多master集群【三、配置k8s master及高可用】

    前面两篇文章已经配置好了etcd和flannel的网络,现在开始配置k8s master集群. etcd集群配置参考:二进制搭建kubernetes多master集群[一.使用TLS证书搭建etcd集 ...

  8. 二进制搭建kubernetes多master集群【一、使用TLS证书搭建etcd集群】

    上一篇我们介绍了kubernetes集群架构以及系统参数配置,参考:二进制搭建kubernetes多master集群[开篇.集群环境和功能介绍] 下面本文etcd集群才用三台centos7.5搭建完成 ...

  9. 二进制搭建kubernetes多master集群【开篇、集群环境和功能介绍】

    本文主要说明kubernetes集群使用组建的版本和功能介绍.. 一.组件版本 Kubernetes 1.12.3 Docker 18.06.1-ce Etcd 3.3.10 Flanneld 0.1 ...

随机推荐

  1. 【LeetCode】1461. 检查一个字符串是否包含所有长度为 K 的二进制子串 Check If a String Contains All Binary Codes of Size K

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 统计长度为 K 的子串个数 日期 题目地址:https ...

  2. LeetCode 第三大的数414. Third Maximum Number

    题目 描述:给定数组中求第三大的数字:如果没有,返回最大的:时间复杂度O(n) 记得<剑指offer>才看到过这样的求第k大的题目.但是忘记具体怎么做了.只好先自己想了. 因为时间复杂度的 ...

  3. 比赛难度(HDU4546)

    比赛难度 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total Submis ...

  4. 为什么别人的 WordPress 网站那么快?

    越来越多的人使用 WordPress 来搭建网站了,W3techs 最新数据统计显示[1],截止到2021年11月3日,全网有43.0%的网站都是基于WordPress 搭建的.那么问题来了,为什么别 ...

  5. 「AHOI2013」 差异

    知识点: SA,线段树,单调栈 原题面 Loj Luogu 题意简述 给定一长度为 \(n\) 的字符串 \(S\),令 \(T_i\) 表示从第 \(i\) 个字符开始的后缀,求: \[\sum_{ ...

  6. Sufficient Statistic (充分统计量)

    目录 定义 充分统计量的判定 最小统计量 例子 Poisson Normal 指数分布 Gamma Sufficient statistic - Wikipedia Sufficient statis ...

  7. [C]郝斌C语言课程大纲及笔记

    本笔记整理于郝斌老师C语言课程,做学习参考之用. 1.[编程笔记]第一章 C语言概述 2.[编程笔记]第二章 C语言预备知识 3.[编程笔记]第三章 运算符与表达式 4.[编程笔记]第四章 流程控制 ...

  8. MySQL高级查询与编程笔记 • 【第5章 常见数据库对象】

    全部章节   >>>> 本章目录 5.1 视图 5.1.1 视图的定义 5.1.2 视图的优点 5.1.3 视图的创建和使用 5.1.4 利用视图解决数据库的复杂应用 5.1. ...

  9. Redis_设置密码

    一.临时设置密码 # 获取密码 config get requirepass # 设置密码为123456 config set requirepass 123456 # 验证密码.当设置密码后,进入r ...

  10. dnspython模块报错 AttributeError: 'CNAME' object has no attribute 'address'

    有时候用到这个模块的时候会报错 AttributeError: 'CNAME' object has no attribute 'address' 如下所示 [root@ansible ch01]# ...