当前位置: 首页 > article >正文

openeuler 22.03 lts sp4 使用 cri-o 和 静态 pod 的方式部署 k8s-v1.32.0 高可用集群

前情提要

整篇文章会非常的长…可以选择性阅读,另外,这篇文章是自己学习使用的,用于生产,还请三思和斟酌

  • 静态 pod 的部署方式和二进制部署的方式是差不多的,区别在于 master 组件的管理方式是 kubectl 还是 systemctl
  • 有 kubeadm 工具,为什么还要用静态 pod 的方式部署?
    • kubeadm init 节点一旦灾难性异常了,整个 k8s 集群都难逃一死(我曾经干活干迷瞪了,要 reset 一个 worker 节点,不小心在 kubeadm init 节点执行 kubeadm reset 命令,整个集群都废了,好在是测试环境,pod 服务都有持久化和相关的 yaml 文件兜底,直接重建 k8s 集群了
  • 有二进制部署的方式,为什么还要用静态 pod 的方式部署?
    • 统一采集 pod 日志也能同时采集 master 组件的日志,不需要额外的处理
    • pod 的 request 和 limit 颗粒度更小,能更好的调整 master 组件的资源使用限制

相关概念

The differences between Docker, containerd, CRI-O and runc

  • 此图概述了 Docker、Kubernetes、CRI、OCI、containerd 和 runc 如何在这个生态系统中协同工作

在这里插入图片描述

OCI

**Open Container Initiative (OCI):**一组容器标准,用于描述镜像格式、运行时和分发的规范

  • OCI 是为容器世界创建一些标准的首批工作之一
  • 它由 Docker 和其他公司于 2015 年成立
  • OCI 得到了许多科技公司的支持,并维护着容器映像格式以及容器应如何运行的规范

CRI

**Container Runtime Interface (CRI) in Kubernetes:**是 Kubernetes 用来控制创建和管理容器的不同运行时的接口

  • CRI 使 Kubernetes 更容易使用不同的容器运行时
  • Kubernetes 项目不需要单独为每个运行时添加支持,而是描述了 Kubernetes 将如何与任何运行时交互
  • 只要给定的容器运行时实现 CRI API,运行时就可以按照自己的喜好创建和启动容器

在这里插入图片描述

containerd 和 CRI-O 都实现了 CRI 规范,都可以用来当作容器运行时

Docker

  • 容器化就是从 Docker 开始的,对很多人来说 “Docker” 这个名字本身就是 “容器” 的代名词
  • 它是管理容器的最流行的工具
  • 现在被命名为 Docker Engine
  • docker 命令实际上调用了一些较低级别的工具来完成繁重的工作

在这里插入图片描述

  • docker-cli:这是一个命令行工具,它是用来完成 docker 命令的 pull, build, run, exec 等进行交互。
  • containerd:这是高级别的容器运行时,一个管理和运行容器的守护进程。它推送和拉取镜像,管理存储和网络,并监督容器的运行。
  • runc:这是低级别的容器运行时(实际创建和运行容器的东西)。它包括 libcontainer,一个用于创建容器的基于 Go 的本地实现。
dockershim

由于 Docker 比 Kubernetes 更早,没有实现 CRI,这就是 dockershim 存在的原因,它支持将 Docker 被硬编码到 Kubernetes 中

  • 随着容器化成为行业标准,Kubernetes 项目增加了对额外运行时的支持,比如通过 Container Runtime Interface (CRI) 容器运行时接口来支持运行容器
  • dockershim 将会从 Kubernetes 1.24 中完全移除
    • 所有支持 OCI 规范的镜像,containerd 和 CRI-O 都可以运行

containerd

containerd 是来自 Docker 的高级容器运行时

  • containerd 诞生于原始 Docker 项目的一部分,之后被捐赠给云原生计算基金会(CNCF)
  • 在内部,Docker Engine 使用 containerd,所以安装 docker 的时候会同时安装 containerd

CRI-O

CRI-O 是一个轻量级的容器运行时

  • 它诞生于 Red Hat、IBM、Intel、SUSE 和其他公司
  • 专注于 Kubernetes 的需求,提供一个精简的容器运行时
  • 由 Kubernetes 使用的 kubelet 来管理和调用,直接通过 Kubernetes 的 CRI 接口与容器交互

runc

一个轻量级的容器运行时,负责创建和运行容器实例,是实现 OCI 接口的最低级别的容器运行时

  • 为容器提供所有低级功能,并与现有的低级 Linux 功能(如命名空间和控制组)交互,使用这些功能来创建和运行容器进程

关系总结

  • runc: 容器运行时的低层实现,负责直接启动和运行容器。
  • containerd: 容器运行时管理工具,负责容器的生命周期管理,通常通过 runc 来启动容器
  • CRI-O: 为 Kubernetes 提供的容器运行时实现
  • Docker: 一个容器平台,使用 containerd 作为容器运行时,包含了容器构建、管理和运行的完整工具链

开始部署

环境介绍

使用 podman 是因为比 docker 更轻量,没有 deamon 守护进程,只是一个命令文件,自身不占用系统资源,配置私有仓库什么的,也不存在重启服务,和 docker 一样的语法

组件 版本
OS openEuler 22.03 (LTS-SP4)
podman 3.4.4
k8s 1.32.0
cri-o 1.23.2
etcd 3.5.16-0
pause 3.10
calico v3.26.0
coredns v1.11.3
haproxy 2.9.8
keepalived 2.3.1

机器 ip 和对应的服务

IP HOSTNAME SERVICE/ROLE
192.168.182.129 k8s-master-01 k8s-master+keepalived+haproxy
192.168.182.130 k8s-master-02 k8s-master+keepalived+haproxy
192.168.182.131 k8s-master-03 k8s-master+keepalived+haproxy
192.168.182.132 k8s-worker-04 k8s-worker
192.168.182.133 k8s-worker-05 k8s-worker
192.168.182.200 / VIP

系统初始化相关

  • 如果是虚拟机还没就绪,可以先启动一台机器,执行完初始化后,直接克隆机器更方便快捷
  • 如果机器已经就绪了,下面的初始化操作,每个机器都需要执行
  • 下面的操作省略了静态 ip 时间同步的操作,大家自己操作一下
关闭防火墙
systemctl disable firewalld --now
关闭 selinux
setenforce 0
sed -i '/SELINUX/s/enforcing/disabled/g' /etc/selinux/config
关闭 swap
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
开启内核模块
# 针对于 kube-proxy 使用 ipvs 模式的
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
# 常规要开启的
modprobe nf_conntrack
modprobe br_netfilter
modprobe overlay
开启模块自动加载服务
cat > /etc/modules-load.d/k8s-modules.conf <<EOF
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
br_netfilter
overlay
EOF

设置为开机自启

systemctl enable systemd-modules-load && systemctl restart systemd-modules-load
sysctl 内核参数调整
cat <<EOF > /etc/sysctl.d/kubernetes.conf
# 开启数据包转发功能(实现vxlan)
net.ipv4.ip_forward=1
# iptables对bridge的数据进行处理
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.bridge.bridge-nf-call-arptables=1
# 不允许将TIME-WAIT sockets重新用于新的TCP连接
net.ipv4.tcp_tw_reuse=0
# socket监听(listen)的backlog上限
net.core.somaxconn=32768
# 最大跟踪连接数,默认 nf_conntrack_buckets * 4
net.netfilter.nf_conntrack_max=1000000
# 禁止使用 swap 空间,只有当系统 OOM 时才允许使用它
vm.swappiness=0
# 计算当前的内存映射文件数。
vm.max_map_count=655360
# 内核可分配的最大文件数
fs.file-max=6553600
# 持久连接
net.ipv4.tcp_keepalive_time=600
net.ipv4.tcp_keepalive_intvl=30
net.ipv4.tcp_keepalive_probes=10
EOF

立即生效

sysctl -p /etc/sysctl.d/kubernetes.conf

特殊操作:/etc/sysctl.conf 文件里面的 net.ipv4.ip_forward=0 会覆盖掉上面子配置文件里面的值

  • 猜测是因为 /etc/openEuler_security/security.conf 文件里面配置了 301@m@/etc/sysctl.conf@net.ipv4.ip_forward=@0,但是不确定修改后会影响哪些,还是打算直接修改 /etc/sysctl.conf ,这样就不会导致机器重启,kubelet 起不来的情况
sed -i 's|^net.ipv4.ip_forward.*|net.ipv4.ip_forward=1|g' /etc/sysctl.conf
sysctl -p
清空 iptables 规则
iptables -F && \
iptables -X && \
iptables -F -t nat && \
iptables -X -t nat && \
iptables -P FORWARD ACCEPT
安装各种依赖和工具
yum install -y vim wget curl tar net-tools jq bash-completion tree bind-utils telnet unzip nc
修改 .bashrc 文件

具体参考我之前的博客:关于 openeuler 22.03-LTS-SP4 scp 失败问题的记录,主要影响的是 scp 命令,具体的,看大家自己选择

安装 kubectl 和 kubelet

k8s 官方也没有 openeuler 的源,但是可以直接使用 kubernetes-el7 的源来安装,下面是配置 kubernetes-el7

  • 由于 Kubernetes 官方变更了仓库的存储路径以及使用方式,如果需要使用 1.28 及以上版本,请使用新版配置方法进行配置:https://developer.aliyun.com/mirror/kubernetes
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.32/rpm/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.32/rpm/repodata/repomd.xml.key
EOF

后面会用 kubeadm 来生成 kubeconfig 证书

  • 新版本的 k8s 仓库,安装 kubeadm 不再自动安装 kubelet 和 kubectl 了,这里需要都装一下
  • 新版本的 k8s 仓库,安装 kubeadm 也不再自动安装 conntrack-tools 了,kube-proxy 依赖 conntrack
yum install -y kubeadm-1.32.0 kubelet-1.32.0 kubectl-1.32.0 conntrack-tools

利用 kubeadm 提前拉取镜像

kubeadm config images pull --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers --kubernetes-version 1.32.0
安装 CRI-O
  • openeuler 的 EPOL 里面自带 cri-o,直接 yum 安装即可
  • crictl 可以在后期用来查看容器状态,日志等操作
yum install -y cri-o crictl
配置 cri-o

默认配置文件是 /etc/crio/crio.conf

[crio.image]
pause_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.10"
# 指定 cgroup 是 systemd,虽然参数默认是 systemd,这里再加一下也不影响
[crio.runtime]
cgroup_manager = "systemd"
# 默认配置文件里面是 true
[crio.metrics]
enable_metrics = false

启动 cri-o 并配置开机自启

systemctl enable crio.service --now
配置 crictl

检查 /etc/crictl.yaml 里面的内容

runtime-endpoint: "unix:///var/run/crio/crio.sock"
安装 podman
yum install -y podman

修改配置文件 /etc/containers/registries.conf

  • 让不带仓库的镜像自动指定 docker.io 这个仓库前缀,podman 不指定镜像仓库的时候,本地显示是 localhost/
unqualified-search-registries = ["docker.io"]

重启 crio,让 crio 重新加载 /etc/containers/registries.conf

systemctl restart crio.service

到这一步,就可以克隆机器了

配置变量

创建目录

mkdir -p /appdata/k8s_deploy

配置变量,避免一些目录名称或者 ip 和我不一样,后面还要一个个检查和修改,后面全部使用变量来替代,尽可能通过 cv 就可以完成部署

cat > /appdata/k8s_deploy/k8s.env <<EOF
# 部署文件的存放目录
deploy_home="/appdata/k8s_deploy"
# k8s master 节点
k8s_master_nodes='
192.168.182.129
192.168.182.130
192.168.182.131
'

# k8s worker 节点
k8s_worker_nodes='
192.168.182.132
192.168.182.133
'

# 所有节点
all_nodes="
\${k8s_master_nodes}
\${k8s_worker_nodes}
"
# 临时的证书目录
cert_dir=\${deploy_home}/certs
# 定义 k8s 目录
k8s_home=/etc/kubernetes
# k8s 证书目录
k8s_cert=\${k8s_home}/certs
# k8s kubeconfig 目录
kubeconfig_dir=\${k8s_home}/kubeconfig
# k8s 静态 pod 目录
manifest_dir=\${k8s_home}/manifests
# k8s 静态 pod 配置文件目录
pod_config=\${k8s_home}/static_pod_config
# k8s 高可用域名,自己定义
k8s_vip_url=apiserver.ha.icu
# k8s 高可用 vip 地址
k8s_vip_ip=192.168.182.200
# haproxy 端口
k8s_vip_port=8443
# 宿主机网卡名称
interface_name=ens33
# k8s svc 的 ip 段
svc_ip_range=10.96.0.1
# apiserver 中 svc 的网段配置,上下两个变量必须是同一个网段的
svc_cluster_ip_range=10.96.0.0/12
# coredns 的 ip 地址
cluster_dns=10.96.0.10
# k8s pod 的网段
cluster_cidr=172.22.0.0/16
EOF

将 source 命令加入到 ~/.bashrc 目录下面,防止终端断开后,变量为空

cat >> ~/.bashrc <<EOF
source /appdata/k8s_deploy/k8s.env
EOF
source ~/.bashrc

证书制作

创建证书目录

for i in ${k8s_master_nodes};do ssh ${i} "mkdir -p ${k8s_cert}";done
mkdir -p ${deploy_home}/{
   templates,certs}
ca 证书
  • 注:kubeadm 在创建证书的时候有三个 ca 证书,我这边就一个 ca 通用了,没啥影响

生成一个 2048 位的 ca.key 文件

openssl genrsa -out ${cert_dir}/ca.key 2048

在 ca.key 文件的基础上,生成 ca.crt 文件(用参数 -days 设置证书有效期,单位:天)

openssl req -x509 -new -nodes \
-key ${cert_dir}/ca.key \
-subj "/CN=kubernetes" \
-addext "keyUsage=critical,digitalSignature,keyEncipherment,keyCertSign" \
-addext "subjectAltName=DNS:kubernetes" \
-days 36525 \
-out ${cert_dir}/ca.crt

检查证书到期时间

openssl x509 -text -in ${cert_dir}/ca.crt | grep -A 2 Validity
分发 ca 证书到所有 master 节点
for i in ${k8s_master_nodes};do scp ${cert_dir}/{
   ca.crt,ca.key} ${i}:${k8s_cert}/;done
apiserver 相关证书
kube-apiserver.crt

生成一个 2048 位的 kube-apiserver.key 文件

cert_name='kube-apiserver'
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048

证书配置文件模板

cat > ${deploy_home}/templates/server-csr.cnf.tmp <<EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[ dn ]
CN = kubernetes

[ req_ext ]
subjectAltName = @alt_names

[ v3_ext ]
basicConstraints=critical, CA:FALSE
keyUsage=critical, digitalSignature, keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName = @alt_names

[ alt_names ]
EOF

生成证书配置文件

sed "s|CN =.*|CN = ${cert_name}|g" ${deploy_home}/templates/server-csr.cnf.tmp > ${cert_dir}/${cert_name}-csr.cnf

idx_num=1;
for dnsurl in ${k8s_master_nodes} ${k8s_vip_ip} ${k8s_vip_url} kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local;
do 
  echo "DNS.${idx_num} = ${dnsurl}" >> ${cert_dir}/${cert_name}-csr.cnf;
  let idx_num++;
done

idx_num=1;
for hostip in ${k8s_master_nodes} ${k8s_vip_ip} ${svc_ip_range};
do 
  echo "IP.${idx_num} = ${hostip}" >> ${cert_dir}/${cert_name}-csr.cnf;
  let idx_num++;
done

生成 kube-apiserver.csr 证书

openssl req -new -key ${cert_dir}/${cert_name}.key \
-out ${cert_dir}/${cert_name}.csr \
-config ${cert_dir}/${cert_name}-csr.cnf

使用 csr 证书和 ca 证书生成 apiserver.crt 证书

openssl x509 -req -sha256 -in ${cert_dir}/${cert_name}.csr \
-CA ${cert_dir}/ca.crt \
-CAkey ${cert_dir}/ca.key \
-CAcreateserial \
-out ${cert_dir}/${cert_name}.crt \
-days 36525 \
-extensions v3_ext \
-extfile ${cert_dir}/${cert_name}-csr.cnf
kube-apiserver-kubelet-client.crt

生成一个 2048 位的 kube-apiserver-kubelet-client.key 文件

cert_name='kube-apiserver-kubelet-client'
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048

证书配置文件模板

cat > ${deploy_home}/templates/client-csr.cnf.tmp <<EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[ dn ]

[ v3_ext ]
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=clientAuth
EOF

生成 kube-apiserver-kubelet-client.csr 证书

cat ${deploy_home}/templates/client-csr.cnf.tmp > ${cert_dir}/${cert_name}-csr.cnf
openssl req -new -key ${cert_dir}/${cert_name}.key \
-out ${cert_dir}/${cert_name}.csr \
-subj "/O=system:masters/CN=${cert_name}" \
-config ${cert_dir}/${cert_name}-csr.cnf

使用 csr 证书和 ca 证书生成 kube-apiserver-kubelet-client.crt 证书

openssl x509 -req -sha256 -in ${cert_dir}/${cert_name}.csr \
-CA ${cert_dir}/ca.crt \
-CAkey ${cert_dir}/ca.key \
-CAcreateserial \
-out ${cert_dir}/${cert_name}.crt \
-days 36525 \
-extensions v3_ext \
-extfile ${cert_dir}/${cert_name}-csr.cnf
front-proxy-client.crt
  • 这个证书是 apiserver 的代理身份验证和身份识别相关的,比如 metrics-server 等代理的部署会依赖

生成一个 2048 位的 front-proxy-client.key 文件

cert_name=front-proxy-client
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048

生成 front-proxy-client.csr 证书

cat ${deploy_home}/templates/client-csr.cnf.tmp > ${cert_dir}/${cert_name}-csr.cnf
openssl req -new -key ${cert_dir}/${cert_name}.key \
-out ${cert_dir}/${cert_name}.csr \
-subj "/CN=${cert_name}" \
-config ${cert_dir}/${cert_name}-csr.cnf

使用 csr 证书和 ca 证书生成 front-proxy-client.crt 证书

openssl x509 -req -sha256 -in ${cert_dir}/${cert_name}.csr \
-CA ${cert_dir}/ca.crt \
-CAkey ${cert_dir}/ca.key \
-CAcreateserial \
-out ${cert_dir}/${cert_name}.crt \
-days 36525 \
-extensions v3_ext \
-extfile ${cert_dir}/${cert_name}-csr.cnf
kube-apiserver-etcd-client.crt

生成一个 2048 位的 kube-apiserver-etcd-client.key 文件

cert_name=kube-apiserver-etcd-client
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048

生成 kube-apiserver-etcd-client.csr 证书

cat ${deploy_home}/templates/client-csr.cnf.tmp > ${cert_dir}/${cert_name}-csr.cnf
openssl req -new -key ${cert_dir}/${cert_name}.key \
-out ${cert_dir}/${cert_name}.csr \
-subj "/O=system:masters/CN=${cert_name}" \
-config ${cert_dir}/${cert_name}-csr.cnf

使用 csr 证书和 ca 证书生成 kube-apiserver-etcd-client.crt 证书

openssl x509 -req -sha256 -in ${cert_dir}/${cert_name}.csr \
-CA ${cert_dir}/ca.crt \
-CAkey ${cert_dir}/ca.key \
-CAcreateserial \
-out ${cert_dir}/${cert_name}.crt \
-days 36525 \
-extensions v3_ext \
-extfile ${cert_dir}/${cert_name}-csr.cnf
sa.pub

生成 sa.key 私钥

openssl genpkey -algorithm RSA -out ${cert_dir}/sa.key -pkeyopt rsa_keygen_bits:2048

生成 sa.pub 公钥文件

openssl rsa -pubout -in ${cert_dir}/sa.key -out ${cert_dir}/sa.pub
验证证书链

返回 ok 说明证书链没问题

for i in kube-apiserver kube-apiserver-etcd-client kube-apiserver-kubelet-client front-proxy-client;do openssl verify -CAfile ${cert_dir}/ca.crt ${cert_dir}/${i}.crt;done
分发 apiserver 相关证书到 master 节点
for i in ${k8s_master_nodes};do scp ${cert_dir}/{
   kube-apiserver*.crt,kube-apiserver*.key,front-proxy-client.crt,front-proxy-client.key,sa.key,sa.pub} ${i}:${k8s_cert}/;done
etcd 相关证书
  • 注:kubeadm 在创建证书的时候,etcd 会创建 server,peer 和 ca 三个证书,ca 已经通用了,剩下的两个证书也可以通用,就不重复创建了

生成一个 2048 位的 kube-etcd.key 文件

cert_name='kube-etcd'
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048

证书配置文件模板

cat > ${deploy_home}/templates/serverAndClient-csr.cnf.tmp <<EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[ dn ]
CN = kubernetes

[ req_ext ]
subjectAltName = @alt_names

[ v3_ext ]
basicConstraints=critical, CA:FALSE
keyUsage=critical, digitalSignature, keyEncipherment
extendedKeyUsage=serverAuth, clientAuth
subjectAltName = @alt_names

[ alt_names ]
EOF

生成证书配置文件

for i in ${k8s_master_nodes};do sed "s|CN =.*|CN = ${i}|g" ${deploy_home}/templates/serverAndClient-csr.cnf.tmp > ${cert_dir}/${cert_name}-${i}-csr.cnf;done
idx_num=1;
for i in ${k8s_master_nodes};
do
  for dnsurl in ${k8s_master_nodes} localhost;
  do
    echo "DNS.${idx_num} = ${dnsurl}" >> ${cert_dir}/${cert_name}-${i}-csr.cnf;
    let idx_num&#

http://www.kler.cn/a/526404.html

相关文章:

  • 【huawei】云计算的备份和容灾
  • 策略梯度 (Policy Gradient):直接优化策略的强化学习方法
  • 你好!这是我自己的CSDN博客!
  • 51单片机开发:串口通信
  • 第十四讲 JDBC数据库
  • mysql.sock.lock 导致mysql重启失败
  • 【2025最新计算机毕业设计】基于SpringBoot+Vue家政呵护到家护理服务平台(高质量源码,可定制,提供文档,免费部署到本地)
  • 创建 priority_queue - 初阶(c++)
  • 如何用 Groq API 免费使用 DeepSeek-R1 70B,并通过 Deno 实现国内访问
  • 青少年编程与数学 02-007 PostgreSQL数据库应用 20课题、连接与ORM
  • Maven的单元测试
  • Deepseek技术浅析(二):大语言模型
  • 【信息系统项目管理师-选择真题】2005下半年综合知识答案和详解
  • 【2024年华为OD机试】 (A卷,100分)- 异常的打卡记录(JavaScriptJava PythonC/C++)
  • 【原创改进】SCI级改进算法,一种多策略改进Alpha进化算法(IAE)
  • SAP系统中的主要采购类型/采购模式总结
  • 应用程序中处理文件上传的方法
  • Linux-Robust-Futex学习笔记
  • Timer计时器
  • 从0到1:C++ 开启游戏开发奇幻之旅(二)
  • 我的求职面经:(1)C++里指针和数组的区别
  • 【异或和之差——Trie,DP】
  • 基于Springboot的社区药房管理系统
  • 【练习】PAT 乙 1023 组个最小数
  • 镭速大文件传输自动选择压缩算法原理
  • 学技术学英语:elasticsearch查询的两阶段queryingfetching