0%

LINQ查询表达式(4) - LINQ Join联接 - 水手哥 - 博客园

Excerpt

内部联接 按照关系数据库的说法,“内部联接”产生一个结果集,对于该结果集内第一个集合中的每个元素,只要在第二个集合中存在一个匹配元素,该元素就会出现一次。 如果第一个集合中的某个元素没有匹配元素,则它不会出现在结果集内。 Join 方法(通过 C# 中的 join 子句调用)可实现内联。 内部连接的


内部联接

  按照关系数据库的说法,“内部联接”产生一个结果集,对于该结果集内第一个集合中的每个元素,只要在第二个集合中存在一个匹配元素,该元素就会出现一次。 如果第一个集合中的某个元素没有匹配元素,则它不会出现在结果集内。 Join 方法(通过 C# 中的 join 子句调用)可实现内联。

  内部连接的4种变体:

  • 简单联接,它基于一个简单的键将来自两个数据源的元素相互关联。
  • 复合联接,它基于一个复合键将来自两个数据源的元素相互关联。 使用复合键(即由多个值组成的键)可以基于多个属性将元素相互关联。
  • 多联接,在其中连续的联接操作被相互拼接在一起。
  • 分组联接

  下面分别描述:

  1. 内部联接:简单键联接

    复制代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
             class<span> Person
    {
    public string FirstName { get; set<span>; }
    public string LastName { get; set<span>; }
    }

    class<span> Pet
    {
    public string Name { get; set<span>; }
    public Person Owner { get; set<span>; }
    }

    /// &lt;summary&gt;
    /// Simple inner join.
    /// &lt;/summary&gt;
    public static void<span> InnerJoinExample()
    {
    // Create a collection of person-pet pairs. Each element in the collection
    // is an anonymous type containing both the person's name and their pet's name.
    var query = from person in<span> people
    join pet in<span> pets on person equals pet.Owner
    select new { OwnerName = person.FirstName, PetName = pet.Name };<br>     }</span></span></span></span></span></span></span></span></span>

    复制代码

  2. 内部联接:复合联接
      与仅仅基于一个属性将元素相互关联不同,使用复合键可基于多个属性来比较元素。 为此,需要为每个集合指定键选择器函数,以便返回一个由要比较的属性组成的匿名类型。 如果给属性加上了标签,则这些属性必须在每个键的匿名类型中都有相同的标签, 而且还必须以相同顺序出现。

    复制代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <span>class</span><span> Employee
    {
    </span><span>public</span> <span>string</span> FirstName { <span>get</span>; <span>set</span><span>; }
    </span><span>public</span> <span>string</span> LastName { <span>get</span>; <span>set</span><span>; }
    </span><span>public</span> <span>int</span> EmployeeID { <span>get</span>; <span>set</span><span>; }
    }

    </span><span>class</span><span> Student
    {
    </span><span>public</span> <span>string</span> FirstName { <span>get</span>; <span>set</span><span>; }
    </span><span>public</span> <span>string</span> LastName { <span>get</span>; <span>set</span><span>; }
    </span><span>public</span> <span>int</span> StudentID { <span>get</span>; <span>set</span><span>; }
    }

    IEnumerable</span>&lt;<span>string</span>&gt; query = <span>from</span> employee <span>in</span><span> employees
    join student </span><span>in</span><span> students
    on </span><span>new</span><span> { employee.FirstName, employee.LastName }
    equals </span><span>new</span><span> { student.FirstName, student.LastName }
    </span><span>select</span> employee.FirstName + <span>"</span> <span>"</span> + employee.LastName;

    复制代码

  3. 内部连接:多联接
        可以将任意数量的联接操作拼接在一起以执行多联接。 C# 中的每一个 join 子句都可将指定的数据源与前一个联接的结果相互关联。

    复制代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
               <span>class</span><span> Person
    {
    </span><span>public</span> <span>string</span> FirstName { <span>get</span>; <span>set</span><span>; }
    </span><span>public</span> <span>string</span> LastName { <span>get</span>; <span>set</span><span>; }
    }
    </span><span>class</span><span> Pet
    {
    </span><span>public</span> <span>string</span> Name { <span>get</span>; <span>set</span><span>; }
    </span><span>public</span> Person Owner { <span>get</span>; <span>set</span><span>; }
    }
    </span><span>class</span><span> Cat : Pet
    { }
    </span><span>class</span><span> Dog : Pet
    { }

    </span><span>//</span><span> The first join matches Person and Cat.Owner from the list of people and
    </span><span>//</span><span> cats, based on a common Person. The second join matches dogs whose names start
    </span><span>//</span><span> with the same letter as the cats that have the same owner.</span>
    <span>var</span> query = <span>from</span> person <span>in</span><span> people
    join cat </span><span>in</span><span> cats on person equals cat.Owner
    join dog </span><span>in</span><span> dogs on
    </span><span>new</span> { Owner = person, Letter = cat.Name.Substring(<span>0</span>, <span>1</span><span>) }
    equals </span><span>new</span> { dog.Owner, Letter = dog.Name.Substring(<span>0</span>, <span>1</span><span>) }
    </span><span>select</span> <span>new</span> { CatName = cat.Name, DogName = dog.Name };

    复制代码

  4. 内部连接:分组联接实现内部联接
         在 query1 中,Person 对象列表基于与 Pet.Owner 属性匹配的 Person 分组联接到 Pet 对象列表。 分组联接创建了一个中间组集合,该集合中的每个组都由一个 Person 对象和匹配的 Pet 对象序列组成。

    复制代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <span>        class</span><span> Person
    {
    </span><span>public</span> <span>string</span> FirstName { <span>get</span>; <span>set</span><span>; }
    </span><span>public</span> <span>string</span> LastName { <span>get</span>; <span>set</span><span>; }
    }

    </span><span>class</span><span> Pet
    {
    </span><span>public</span> <span>string</span> Name { <span>get</span>; <span>set</span><span>; }
    </span><span>public</span> Person Owner { <span>get</span>; <span>set</span><span>; }
    }

    </span><span> var</span> query1 = <span>from</span> person <span>in</span><span> people
    join pet </span><span>in</span><span> pets on person equals pet.Owner into gj
    </span><span>from</span> subpet <span>in</span><span> gj
    </span><span>select</span> <span>new</span> { OwnerName = person.FirstName, PetName = subpet.Name };

    复制代码

    分组联接示例:执行分组联接以创建 XML 的示例

    分组联接非常适合于使用 LINQ to XML 来创建 XML。 本例结果选择器函数创建表示已联接对象的 XML 元素,而不是创建匿名类型。

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<span>     class</span><span> Person
{
</span><span>public</span> <span>string</span> FirstName { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> LastName { <span>get</span>; <span>set</span><span>; }
}
</span><span>class</span><span> Pet
{
</span><span>public</span> <span>string</span> Name { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> Person Owner { <span>get</span>; <span>set</span><span>; }
}

  XElement ownersAndPets </span>= <span>new</span> XElement(<span>"</span><span>PetOwners</span><span>"</span><span>,
</span><span>from</span> person <span>in</span><span> people
join pet </span><span>in</span><span> pets on person equals pet.Owner into gj
</span><span>select</span> <span>new</span> XElement(<span>"</span><span>Person</span><span>"</span><span>,
</span><span>new</span> XAttribute(<span>"</span><span>FirstName</span><span>"</span><span>, person.FirstName),
</span><span>new</span> XAttribute(<span>"</span><span>LastName</span><span>"</span><span>, person.LastName),
</span><span>from</span> subpet <span>in</span><span> gj
</span><span>select</span> <span>new</span> XElement(<span>"</span><span>Pet</span><span>"</span>, subpet.Name)));&nbsp;&nbsp;

复制代码

外部连接

  • 外部联接(左外部联接)

     左外部联接是这样一个联接:在其中返回第一个集合的每个元素,而无论该元素在第二个集合中是否具有相关元素。 可以使用 LINQ 执行左通过对分组联接的结果调用方法 DefaultIfEmpty 外部联接连接。

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<span>class</span><span> Person
{
</span><span>public</span> <span>string</span> FirstName { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> LastName { <span>get</span>; <span>set</span><span>; }
}

</span><span>class</span><span> Pet
{
</span><span>public</span> <span>string</span> Name { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> Person Owner { <span>get</span>; <span>set</span><span>; }
}

     </span><span>//</span><span> Create two lists.</span>
List&lt;Person&gt; people = <span>new</span> List&lt;Person&gt;<span> { magnus, terry, charlotte, arlene };
List</span>&lt;Pet&gt; pets = <span>new</span> List&lt;Pet&gt;<span> { barley, boots, whiskers, bluemoon, daisy };

</span><span>var</span> query = <span>from</span> person <span>in</span><span> people
join pet </span><span>in</span><span> pets on person equals pet.Owner into gj
</span><span>from</span> subpet <span>in</span><span> gj.DefaultIfEmpty()
</span><span>select</span> <span>new</span> { person.FirstName, PetName = (subpet == <span>null</span> ? String.Empty : subpet.Name) };

复制代码

  •  交差联接
1
2
3
4
       <span>var</span> crossJoinQuery =
<span>from</span> c <span>in</span><span> categories
</span><span>from</span> p <span>in</span><span> products
</span><span>select</span> <span>new</span> { c.ID, p.Name };
  • 自定义的联接操作

复制代码

1
2
3
4
5
6
<span>           var</span> nonEquijoinQuery =
<span>from</span> p <span>in</span><span> products
let catIds </span>= <span>from</span> c <span>in</span><span> categories
</span><span>select</span><span> c.ID
</span><span>where</span> catIds.Contains(p.CategoryID) == <span>true</span>
<span>select</span> <span>new</span> { Product = p.Name, CategoryID = p.CategoryID };

复制代码

参考

  [1] MSDN,执行内部连接

  [2] MSDN,执行分组连接

1. 准备

1.1 依赖
Mosquitto apt-get 安装 边缘端
GO https://dl.google.com/go/go1.16.2.linux-amd64.tar.gz 云端、边缘端
K8S 云端
KubeEdge https://github.com/kubeedge/kubeedge/releases 云端、边缘端
Docker 官方安装脚本安装 云端、边缘端
1.2 禁用防火墙
1
sudo systemctl disable firewalld
1.3 永久禁用SELinux
1
2
sudo sed -i 's/SELINUX=permissive/SELINUX=disabled/' /etc/sysconfig/selinux
SELINUX=disabled
1.4 关闭SWAP
1
2
sudo sed -i 's/.*swap.*/#&/' /etc/fstab
#/dev/mapper/centos-swap swap swap defaults 0 0

2. Docker 安装

2.1 卸载旧版本
1
sudo apt-get remove docker docker-engine docker.io containerd runc
2.2 设置仓库
1
2
3
4
5
6
7
8
9
10
sudo lsb_release -cs # 查看系统版本
sudo curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
sudo cat <<EOF >/etc/apt/sources.list.d/docker.list
deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable
deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/ $(lsb_release -cs) stable
deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable
deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu $(lsb_release -cs) stable
deb [arch=amd64] https://repo.huaweicloud.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable
deb [arch=amd64] http://mirrors.cloud.tencent.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable
EOF
2.3 Docker镜像加速

打开/etc/docker/daemon.json,增加或修改registry-mirrors节点

示例:

1
2
3
4
5
6
7
8
{
"registry-mirrors": [
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn",
"https://registry.docker-cn.com"
]

}
2.4 安装
1
2
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
2.5 设 置机启动
1
2
sudo systemctl enable docker 
sudo systemctl start docker.service

3. K8S 安装

3.1 添加源
1
2
3
4
5
6
7
8
9
sudo curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - 
sudo cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
deb https://mirrors.ustc.edu.cn/kubernetes/apt/ kubernetes-xenial main
deb https://mirrors.tuna.tsinghua.edu.cn/kubernetes/apt/ kubernetes-xenial main
deb https://mirrors.cloud.tencent.com/kubernetes/apt kubernetes-xenial main
deb https://repo.huaweicloud.com/kubernetes/apt/ kubernetes-xenial main
EOF
apt-get update
3.2 安装
1
2
sudo apt-get update 
sudo apt-get install kubelet kubeadm kubectl
3.3 配置内核参数
1
2
3
4
5
6
7
8
9
sudo cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
vm.swappiness=0
EOF

sysctl --system
modprobe br_netfilter
sysctl -p /etc/sysctl.d/k8s.conf
3.4 初始化
1
sudo kubeadm init --pod-network-cidr 192.168.50.0/24 --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers

然后执行 docker images 命令查看需要的镜像是否都准备好了。

1
2
3
4
5
6
7
8
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
k8s.gcr.io/kube-proxy v1.17.2 cba2a99699bd 2 weeks ago 116MB
k8s.gcr.io/kube-apiserver v1.17.2 41ef50a5f06a 2 weeks ago 171MB
k8s.gcr.io/kube-controller-manager v1.17.2 da5fd66c4068 2 weeks ago 161MB
k8s.gcr.io/kube-scheduler v1.17.2 f52d4c527ef2 2 weeks ago 94.4MB
k8s.gcr.io/coredns 1.6.5 70f311871ae1 3 months ago 41.6MB
k8s.gcr.io/etcd 3.4.3-0 303ce5db0e90 3 months ago 288MB
k8s.gcr.io/pause
3.5 创建配置文件
1
2
3
sudo mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
3.6 安装calico插件
1
2
3
4
5
kubectl apply -f https://docs.projectcalico.org/v3.11/manifests/calico.yaml

#查看
kubectl get nodes
kubectl get pods --all-namespaces
3.7 安装Dashboard

下载https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta5/aio/deploy/recommended.yaml文件并修改其中部分内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#增加直接访问端口
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
type: NodePort #增加
ports:
- port: 443
targetPort: 8443
nodePort: 30008 #增加
selector:
k8s-app: kubernetes-dashboard
3.7.2 创建证书
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mkdir dashboard-certs

cd dashboard-certs/

#创建命名空间
kubectl create namespace kubernetes-dashboard

# 创建key文件
openssl genrsa -out dashboard.key 2048

#证书请求
openssl req -days 36000 -new -out dashboard.csr -key dashboard.key -subj '/CN=dashboard-cert'

#自签证书,可能报错不要管他
openssl x509 -req -in dashboard.csr -signkey dashboard.key -out dashboard.crt

#创建kubernetes-dashboard-certs对象
kubectl create secret generic kubernetes-dashboard-certs --from-file=dashboard.key --from-file=dashboard.crt -n kubernetes-dashboard
3.7.3 安装Dashboard
1
2
3
4
5
6
7
#安装
kubectl create -f $HOME/recommended.yaml

#检查结果
kubectl get pods -A -o wide

kubectl get service -n kubernetes-dashboard -o wide
3.7.4 创建Dashboard管理员
1
2
# 创建账号
vi $HOME/dashboard-admin.yaml
1
2
3
4
5
6
7
8
# 写入
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: dashboard-admin
namespace: kubernetes-dashboard
1
2
#保存退出后执行
kubectl create -f $HOME/dashboard-admin.yaml

为用户分配权限

1
vi $HOME/dashboard-admin-bind-cluster-role.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-admin-bind-cluster-role
labels:
k8s-app: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: dashboard-admin
namespace: kubernetes-dashboard
1
2
#保存退出后执行
kubectl create -f $HOME/dashboard-admin-bind-cluster-role.yaml
3.7.5 访问
1
2
# 查看并复制token
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep dashboard-admin | awk '{print $1}')

访问 https://{ip}:30008,输入token即可

4. GoLang 环境搭建

4.1 安装golang
1
2
wget https://dl.google.com/go/go1.16.2.linux-amd64.tar.gz
sudo tar -zxvf go1.16.2.linux-amd64.tar.gz -C /usr/local

添加环境变量

编辑/etc/profile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#文件末尾加上
export GOROOT=/usr/local/go
export GOPATH=/data/gopath
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

#刷新
source /etc/profile
mkdir -p /data/gopath && cd /data/gopath
mkdir -p src pkg bin

#配置root 环境变量
sudo vim /etc/sudoers
#在secure_path中添加
:/usr/local/go/bin:/data/gopath/bin

#检查go环境
go version
4.2 安装make gcc
1
sudo apt-get install make gcc

5. 安装KubeEdge

5.1 部署CloudCore
5.1.1 下载源码
1
git clone https://github.com/kubeedge/kubeedge.git $GOPATH/src/github.com/kubeedge/kubeedge

编译云端

1
2
cd $GOPATH/src/github.com/kubeedge/kubeedge/
make all WHAT=cloudcore
5.1.2 编译keadm
5.1.3 创建cloud节点
5.1.4 查看CloudCore状态
5.1.5 获取Token

本系列的源码分析是在 commit da92692baa660359bb314d89dfa3a80bffb1d26c 之上进行的。

cloudcore 部分的源码分析是在 kubeedge 源码分析系列之整体架构基础上展开的,如果没有阅读过 kubeedge 源码分析系列之整体架构,直接阅读本文,会感觉比较突兀。

本文对 edgecore 的 metamanager 模块进行剖析,metamanager 作为 edgecore 中的 edged 模块与 edgehub 模块进行交互的桥梁,除了将 edgehub 的消息转发给 edged,还对一些必要的数据通过 SQLite 进行了缓存,在某种程度上实现了 kubeedge 的 offline mode。本文就对 metamanager 所涉及的 SQLite 数据库相关逻辑和业务逻辑进行剖析:

1、metamanager 数据库相关逻辑剖析

2、metamanager 业务逻辑剖析

metamanager 数据库相关逻辑剖析

从 eventbus 的模块注册函数入手:

kubeedge/edge/pkg/eventbus/event_bus.go

1

复制代码

在注册函数 Register()中,做了 2 件事:

  1. 在 SQLite 中的数据库中初始化 metaManager 表
1
dbm.RegisterModel(MetaManagerModuleName, new(dao.Meta))

复制代码

  1. 注册已经初始化的 metamanager
1
core.Register(&metaManager{})

复制代码

下面深入剖析 “1.在 SQLite 中的数据库中初始化 metaManager 表” 相关内容,进入 dbm.RegisterModel(…):

kubeedge/edge/pkg/common/dbm/db.go

1

复制代码

RegisterModel(…)函数是对 github.com/astaxie/bee…的封装,本文就不跟进去剖析了,感兴趣的同学可以在本文的基础上自行剖析。

回到“1.在 SQLite 中的数据库中初始化 metaManager 表”,深入剖析 metaManager 表的具体定义 dao.Meta,进入 dao.Meta 定义:

kubeedge/edge/pkg/metamanager/dao/meta.go

1

复制代码

metaManager 表的具体定义包含 Key、Type 和 Value 三个字段,具体含义如下:

  • Key meta 的名字;

  • Type meta 对应的操作类型;

  • Value 具体的 meta 值;

与 Meta Struct 的定义在同一文件内,还有对 metaManager 表的一些操作定义,如 SaveMeta、DeleteMetaByKey、UpdateMeta、InsertOrUpdate、UpdateMetaField、UpdateMetaFields、QueryMeta、QueryAllMeta,本文不对具体操作的定义进行深入剖析,感兴趣的同学可以在本文的基础上自行剖析。

metamanager 业务逻辑剖析

从 metamanager 的模块启动函数入手:

kubeedge/edge/pkg/metamanager/module.go

1
func (m *metaManager) Start(c *context.Context) { 

复制代码

启动函数 Start(…)做了如下 4 件事:

  1. 接收并保存模块启动时传入的*context.Context 实例

  2. 初始化 metamanager 配置

  3. 启动一个 goroutine 同步心跳信息

  4. 启动一个循环处理各种事件

接下来展开分析 2、3、4。

初始化 metamanager 配置

进入 InitMetaManagerConfig()定义:

kubeedge/edge/pkg/metamanager/msg_processor.go

1

复制代码

在初始化 metamanager 配置时,从配置文件中获取了 metamanager.context-send-group、metamanager.edgesite、metamanager.context-send-module,根据获取的值对相关变量进行设置。

启动一个 goroutine 同步心跳信息

kubeedge/edge/pkg/metamanager/module.go

1
go func() {

复制代码

在同步心跳信息的 goroutine 中,做了如下 2 件事:

  1. 获取通信心跳的时间间隔
1
period := getSyncInterval()

复制代码

  1. 创建定时器,并定时发送心跳信息
1
timer := time.NewTimer(period) for {...}

复制代码

启动一个循环处理各种事件

进入 m.mainLoop()定义:

kubeedge/edge/pkg/metamanager/msg_processor.go

1
func (m *metaManager) mainLoop() { 

复制代码

mainLoop()函数启动了一个 for 循环,在循环中主要做了 2 件事:

  1. 接收信息
1
msg, err := m.context.Receive(m.Name())

复制代码

  1. 对接收到的信息进行处理

想弄明白对信息的处理过程,需要进入 m.process(…)的定义:

kubeedge/edge/pkg/metamanager/msg_processor.go

1
func (m *metaManager) process(message model.Message) {       

复制代码

process(…)函数中主要做了如下 2 件事:

  1. 获取消息的操作的类型
1
operation := message.GetOperation()

复制代码

  1. 根据信息操作类型对信息进行相应处理

信息的操作类型包括 insert、update、delete、query、response、publish、meta-internal-sync、action、action_result 等,本文不对信息的具体处理过程剖析,感兴趣的同学可以在本文的基础上自行剖析。

Kubeedge 1.5 部署指南

本文介绍了如何在一台ubuntu18.04和一台装有ubuntu20.04的树莓派4B上部署kubeedge1.5的过程。

其中ubuntu作为cloud节点,树莓派4B作为edge节点。

1. 系统配置

1.1 集群环境

主机名

系统

ip

角色

负载

master-node

ubuntu18.04

10.112.55.6

cloud

k8s、docker、cloudcore

ly-desktop

rapsberry pi 4 +ubuntu20.04

10.112.139.139

edge

docker、edgecore

可以通过sudo hostnamectl set-hostname edge-node-1改一下主机名。但是ly-desktop重启后自动变成了ly-desktop,目前还没发现原因

1.2 关闭防火墙和自启动

1
2
sudo systemctl stop ufw
sudo systemctl disable ufw

1.3 禁用SELinux

编辑文件/etc/selinux/config,将SELINUX修改为disabled,如下:

官网1.3 上说这样能关,但是我的系统上没有这个文件,我就没管它,后面好像也没报错

1.4 关闭系统Swap

在 /etc/fstab 中将swap那一行注释掉

1
2
sed -i 's/.*swap.*/#&/' /etc/fstab
sudo swapoff -a

通过free -m 看到swap的确被关闭了

1.5 安装docker

卸载旧版本

1
sudo apt-get remove docker  docker-engine  docker.io

安装相关软件

1
2
sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

增加软件源GPG密钥

1
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add 

添加软件源(unbuntu 是amd、树莓派是arm)

1
2
3
4
sudo add-apt-repository \
"deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \
$(lsb_release -cs) \
stable"

安装

1
2
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

docker换源阿里云

1
2
3
4
5
6
7
8
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://fmrhlw3f.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

1.6 重启

1
sudo reboot

2. cloud节点部署k8s

2.1 安装kubeadm、kubeadm、kubectl

1
2
3
4
5
6
7
8
9
10
sudo apt-get update && sudo apt-get install -y ca-certificates curl software-properties-common apt-transport-https curl
curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -

sudo tee /etc/apt/sources.list.d/kubernetes.list <<EOF
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

2.2 配置内核参数

1
2
3
4
5
6
7
8
9
sudo cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
vm.swappiness=0
EOF

sysctl --system
modprobe br_netfilter
sysctl -p /etc/sysctl.d/k8s.conf

2.3 初始化master

使用阿里云的镜像,然后使用了非默认的CIDR(与宿主机局域网CIDR不一样)

1
2
sudo kubeadm init --pod-network-cidr 172.16.0.0/16 \
--image-repository registry.cn-hangzhou.aliyuncs.com/google_containers

上面的命令执行成功后,会输出一条和kubeadm join相关的命令

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

2.4 安装calico插件

1
kubectl apply -f https://docs.projectcalico.org/v3.11/manifests/calico.yaml

2.5 查看

1
2
kubectl get nodes
kubectl get pods --all-namespaces

2.6 dashboard安装

2.6.1 创建recommended.yaml

在 ~目录下新建 recommended.yaml 文件,复制下面链接的文件进去

https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta5/aio/deploy/recommended.yaml

并且将其中部分内容进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#增加直接访问端口
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
type: NodePort #增加
ports:
- port: 443
targetPort: 8443
nodePort: 30008 #增加
selector:
k8s-app: kubernetes-dashboard
2.6.2 创建证书
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mkdir dashboard-certs

cd dashboard-certs/

#创建命名空间
kubectl create namespace kubernetes-dashboard

# 创建key文件
openssl genrsa -out dashboard.key 2048

#证书请求
openssl req -days 36000 -new -out dashboard.csr -key dashboard.key -subj '/CN=dashboard-cert'

#自签证书,可能报错不要管他
openssl x509 -req -in dashboard.csr -signkey dashboard.key -out dashboard.crt

#创建kubernetes-dashboard-certs对象
kubectl create secret generic kubernetes-dashboard-certs --from-file=dashboard.key --from-file=dashboard.crt -n kubernetes-dashboard
2.6.3 安装dashboard
1
2
3
4
5
6
7
#安装
kubectl create -f ~/recommended.yaml

#检查结果
kubectl get pods -A -o wide

kubectl get service -n kubernetes-dashboard -o wide

image-20201212195847125

2.6.4 创建dashboard管理员

创建账号

1
vi dashboard-admin.yaml
1
2
3
4
5
6
7
8
# 写入
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: dashboard-admin
namespace: kubernetes-dashboard
1
2
#保存退出后执行
kubectl create -f dashboard-admin.yaml

为用户分配权限

1
vi dashboard-admin-bind-cluster-role.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-admin-bind-cluster-role
labels:
k8s-app: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: dashboard-admin
namespace: kubernetes-dashboard
1
2
#保存退出后执行
kubectl create -f dashboard-admin-bind-cluster-role.yaml
2.6.5 web访问

查看并复制token

1
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep dashboard-admin | awk '{print $1}')

访问 https://10.112.55.6:30008,输入token即可

image-20201212200934073

3. cloud节点部署cloudcore

3.1 安装golang

下载 https://golang.google.cn/dl/go1.15.6.linux-amd64.tar.gz 并上传到ubuntu上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
tar -zxvf go1.14.4.linux-amd64.tar.gz -C /usr/local 

#配置用户环境
sudo vim /etc/profile
#文件末尾加上
export GOROOT=/usr/local/go
export GOPATH=/data/gopath
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
#刷新
source /etc/profile
mkdir -p /data/gopath && cd /data/gopath
mkdir -p src pkg bin

#配置root 环境变量
sudo vim /etc/sudoers
#在secure_path中添加
:/usr/local/go/bin:/data/gopath/bin

#检查go环境
go --version
sudo go --version

3.2安装 make、gcc

1
sudo apt-get install make gcc

如果报错显示

1
Depends: *** (= 4:9.3.0-1ubuntu2) but 4:10.2.0-1ubuntu1 is to be installed

可以尝试通过 sudo apt purge *** 然后重新安装

如果sudo apt purge ***不被允许,可以尝试将ubuntu换回原来的国外源,重新安装

3.3 部署cloudcore

3.3.1 下载源码
1
git clone https://gitee.com/ly10208/kubeedge.git $GOPATH/src/github.com/kubeedge/kubeedge
3.3.2 编译keadm
1
2
3
cd $GOPATH/src/github.com/kubeedge/kubeedge
make all WHAT=keadm
#若make报错没有权限可以考虑 在前面加上sudo
3.3.3 创建cloud节点
1
./_output/local/bin/keadm init

一般来说如果ubuntu不能科学上网的话,就会提示连接失败。

需要提前去 https://github.com/kubeedge/kubeedge/releases/tag/v1.5.0 下载 kubeedge-v1.5.0-linux-amd64.tar.gz 。

然后将其上传到 /etc/kubeedge/ 路径下

命令行提示什么下载不下来,手动去下载就行,并且上传到ubuntu的指定位置就行,当然需要一台能够科学上网的机器

最后./_output/local/bin/keadm init执行成功,显示如下

1
2
3
4
Kubernetes version verification passed, KubeEdge installation will start...
...
KubeEdge cloudcore is running, For logs visit: /var/log/kubeedge/cloudcore.log
CloudCore started

3.4 查看cloudcore状态

systemctl status cloudcore 查看运行情况

journalctl -u cloudcore -n 100 查看最近100条日志

运行正常即可

3.5 获取token

1
2
3
./_output/local/bin/keadm gettoken
#输出
93fc5ef46596b60019009205bb6c33f98c7eef74f4e3edbfb4bdc29ea8baa71b.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDc3OTU3ODV9.8fIHabQ2jqY4SJL9fziKXYeqwmTY0-F9rPtXyWRFYiE

4. edge节点部署edgecore

4.1. 安装golang

下载 https://golang.google.cn/dl/go1.15.6.linux-armd64.tar.gz 并上传到树莓派上,注意树莓派是arm架构的cpu,需要下载arm版本的压缩包

剩下的和 3.1 一模一样

4.2安装 make、gcc

与3.2一模一样

4.3 部署edgecore

4.3.1 下载源码,并编译

同上

4.3.2 创建edge节点
1
2
cd $GOPATH/src/github.com/kubeedge/kubeedge
./_output/local/bin/keadm join --cloudcore-ipport="10.112.55.6:10000" --edgenode-name="edge-node-1" --token=93fc5ef46596b60019009205bb6c33f98c7eef74f4e3edbfb4bdc29ea8baa71b.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDc3OTU3ODV9.8fIHabQ2jqY4SJL9fziKXYeqwmTY0-F9rPtXyWRFYiE

需要提前去 https://github.com/kubeedge/kubeedge/releases/tag/v1.5.0 下载 kubeedge-v1.5.0-linux-arm64.tar.gz 。下载arm压缩包

然后将其上传到 /etc/kubeedge/ 路径下

依旧如上3.3.3一样缺啥补啥。

4.4 查看edgecore状态

systemctl status edgecore 查看运行情况

journalctl -u edgecore -n 100 查看日志

运行正常即可

5. 配置完成

去cloud节点查看

1
2
3
4
ly@master-node:/$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
edge-node-1 Ready agent,edge 14h v1.19.3-kubeedge-v1.5.0
master-node Ready master 2d17h v1.19.4

可以发现edge-node-1节点已经加入了
在这里插入图片描述
但是edge节点的两个pod都有问题,目前还不知道解决方法,不过似乎不影响使用

6.参考资料

基于Ubuntu 20.04安装Kubernetes 1.18

KubeEdge v1.3部署指南

kubernetes部署dashboard

ubuntu安装docker

Kubernetes Dashboard的安装与坑 - 简书

Excerpt

1.前言 Kubernetes Dashboard is a general purpose, web-based UI for Kubernetes clusters. I…


ClusterIP

(注:本文为转载,原文:Kubeedge实现原理,感谢原作者)


可能由于各自的定位不同,K3S更像是一个kubernetes厂商的一个发行版,在边缘计算方面其实是没有摄入细节的。相比起来,kubeedge目标更明确,除了在kubernetes的方面做了各种异步通信通道,保障offline后的业务连续性之外;还定义了一系列的设备抽象,用来管理边缘设备。而且,其v1.0版本正朝着边缘端服务网格,以及函数式计算等方向发展。

也是为了切入这个方向,真正理解kubedge的目标和定位,我专门在华为云上申请开通了还在公测阶段的IEF服务。总体感觉是基本功能可用,但是更深入的功能有待开发,比如函数式计算这块貌似只有界面,但是使用手册和功能都缺失等。

这里记录一下最近学习kubeedge v1.0分支代码的一些理解,便于后续快速查看。其实华为在该项目中文档还算写的比较完备的,官方文档可以参考这里

整体架构图比较明了,在不考虑edgesite的情况下,其架构分为了云端和边缘端。其实可以理解为kubernetes的管理侧和kubelet节点侧(对应edge端)。我记得很多年前rancher就采用这种架构,通过隧道网络通过一个机头来统一纳管位于多个云服务提供商上的计算节点。但是请注意,这里的场景是边缘计算,意味着edge端的网络环境难以保障。

云边通信

于是就衍生出了cloud端的cloud Hub与edge端的Edge Hub。这两个模块之间通过websocket或者quic通信,相当于建立了一条底层通信隧道,供k8s和其他应用通信。当然,使用什么协议通信不是重点,重点是如何保障当着之间的链路都无法保障的时候,业务不受到影响,这就是MetaManager的要解决的问题了。

  • CloudHub
    前面提到cloud端的cloudHub就是一个隧道的server端,用于大量的edge端基于websocket或者quic协议连接上来;没错,这货才是正儿八经的二传手,每天就负责拉皮条。

  • EdgeHub
    位于edge端运行,是隧道的client端,负责将接收到的信息转发到各edge端的模块处理;同时将来自个edge端模块的消息通过隧道发送到cloud端。

边缘端

  • MetaManager
    MetaManager模块后端对应一个本地的数据库(sqlLite),所有其他模块需要与cloud端通信的内容都会被保存到本地DB种一份,当需要查询数据时,如果本地DB中存在该数据,就会从本地获取,这样就避免了与cloud端之间频繁的网络交互;同时,在网络中断的情况下,本地的缓存的数据也能够保障其稳定运行(比如你的智能汽车进入到没有无线信号的隧道中),在通信恢复之后,重新同步数据。

  • Edged
    之前提到过kubernetes的kubelet,它相当于k8s的核心。这块其实简单做了一些裁剪,去掉一些用不上的功能,然后就成为Edged模块,该模块就是保障cloud端下发的pod以及其对应的各种配置、存储(后续会支持函数式计算)能够在edge端稳定运行,并在异常之后提供自动检测、故障恢复等能力。当然,由于k8s本身运行时的发展,该模块对应支持各种CRI应该也比较容易。

  • EventBus/ServiceBus/Mappper
    前面讲到的模块都与k8s直接或间接相关,接下来说下与设备(或者说真正IOT业务)相关的设备管理侧。外部设备的接入当前支持MQTT和Rest-API,这里分别对应EventBusServiceBus。EventBus就是一个MQTT broker的客户端,主要功能是将edge端各模块通信的message与设备mapper上报到MQTT的event做转换的组件;而ServiceBus就是对应当外部是Rest-API接入时的转换组件。说道这里,就有必要提一下MQTT broker,其实搞互联网的基本都用过类似于rabbitmq、activeMQ之类的消息中间件,其实他们就支持MQTT协议啦(可以理解为AMQP的精简版)。IOT的各种设备可能直接支持MQTT,但有的只支持蓝牙或者其他近场通信的协议。没关系,Mappper可以实现将各种协议转换为对MQTT的订阅与发布,从而实现与edge端的通信。当然,ServiceBus对应就适用于支持http协议的服务了。

  • DeviceTwin
    edge端最后就剩下一个DeviceTwin模块了,要理解这个名词,就得提一下数字孪生这个概念。这里来科幻一下,假设人类要实现乾坤大挪移,但是有点难度的是,这下是要把你移到火星上。怎么办?这里有一个解决方案:在地球上通过扫描你的所有生物信息,生成拥有你完整生物特征的数据包之后,然后在地球上就把你毁灭了。再将描述你完整信息的数据包通过电波光速发送到火星上,让火星的设备再使用接收到的生物特征造出一个你。是不是挺可行!^_^ 回个头来,我们要说的数字孪生就是那个用来传输到火星的用于描述你所有生物特征的数据包;当然,这里对应就是接入设备信息。所以,DeviceTwin就是将这些信息保存到本地DB中,并处理基于cloud端的操作来修改device的某些属性(也就是操作设备);同时,将设备基于eventBus上报的状态信息同步到本地DB和cloud端的中间人。

云端

  • Controller
    然后再说controller,其实准确的说controller是包括了用于edge端与API-Server同步信息的edgeController与用于DeviceTwin与API-Server同步device CRD信息的deviceController组成。这两个模块相对也比较简单,后面具体讲解。

边缘端

入口与beehive

beehive模块在整个kubeedge中扮演了非常重要的作用,它实现了一套Module管理的接口,程序中各个模块的启动、运行、模块间的通信等都是由其统一封装管理。下图是kubeedge的edge端代码的main启动流程,这里涉及到的modules就是由beehive提供。

可以看到,在初始化的时候,分别加载了各个edge端modules的init函数,用来注册其modules到heehive框架中。然后在core.Run中遍历启动(StartModules)。

另外,值得提及的是,用于模块间通信,发送message到group/module的功能,在beehive中,其实是通过channel来通信的。这也是golang推荐的goroutine间通信的方式。

EdgeHub

重点是启动了两个go routine,用来实现往两个方向的消息接收和分发。这里go ehc.routeToEdge对应从隧道端点接收cloud端发往edge端的消息,然后调用ehc.dispatch解析出消息的目标module并基于heehive模块module间消息的通信机制来转发出去。

同理,go ehc.routeToCloud实现将edge端消息基于隧道转发到cloud端的cloudHub模块处理。当然,该模块中实现了对同步消息的response等到超时处理的逻辑,当在未超时期间收到response消息,会转发到消息发送端模块。比较暴力的是,一旦发送消息到cloud失败,该goroutine会退出,通知所有模块,当前与cloud端是未连接状态,然后重新发起连接。

metaManager在与cloud的连接断开期间,会使用本地DB中的数据,不会发起往cloud端的查询。

Edged

这块基本是调用kubelet的代码,实现较多的是启动流程。另外,将之前kubelet的client作为fake的假接口,转而将数据都通过metaClient来存储数据到metaManager,从而代理之前直接访问api-server的操作。这块的学习可以参考之前分析kubelet架构的一篇文章Kubelet源码架构简介

这里差异化的一块代码在e.syncPod的实现,通过读取metaManager和EdgeController的pod任务列表,来执行对本地pod的操作。同时,这些pod关联的configmap和secret也会随着处理pod的过程而一并处理。对pod的操作也是基于一个操作类别的queue,比如e.podAddWorkerRun就启动了一个用于消费添加pod的queue的goroutine。外部的封装基本就这样,内部完全通过引用kubelet原生的包来处理。

MetaManager

从代码架构看起来,该模块比较简单,首先在外层按照一定周期给自己发送消息,触发定时同步pod状态到cloud端。另外,在mainLoop中启动一个独立的goroutine接收外部消息,并执行处理逻辑。

处理逻辑基于消息类型分类,分别包括:

  1. cloud端发起的增、删、查、改
  2. edge端模块发起的查询请求(前面提到,当状态为disconnect的时候不发起remote查询)
  3. cloud端返回的查询响应的结果
  4. edgeHub发来的用于更新与cloudHub见连接状态的消息
  5. 自己给自己发送的,定期同步edge端pod状态到cloud端的消息
  6. 函数式计算相关的消息

重点来说增删查改,拿添加举例。当接收到要添加某个资源时,会将资源解析出来,组织成为key、type、value的三元组,以一种类似于模拟NoSQL的方式保存到本地的SqlLite数据库中。这样保存的目的也是为了方便快速检索和增删。保存完之后,需要对应发送response消息到请求消息的源模块。

EventBus与ServiceBus

  • EventBus

eventBus用于对接MQTT Broker与beehive,MQTT broker有几种启动模式,从代码实现的角度分为:

  1. 使用内嵌MQTT broker
  2. 使用外部MQTT broker

在内嵌MQTT broker模式下,eventBus启动了golang实现的broker包gomqtt用来作为外部MQTT设备的接入,具体用法请参考其github项目首页。两种模式下eventBus都做了一些共性的操作,具体包括:

  1. 向broker订阅关注的topic,如下:

    复制代码

    SubTopics = []string{ “$hw/events/upload/#”, “$hw/events/device/+/state/update”, “$hw/events/device/+/twin/+”, “$hw/events/node/+/membership/get”, “SYS/dis/upload_records”,
    }

    复制代码

  2. 当接收到对应的event时,触发回调函数onSubscribe

  3. 回调函数中,对event做了简单的分类,分别发送到不同的目的地(DeviceTwin或EventHub)

所有 $hw/events/device/+/twin/+和 $hw/events/node/+/membership/gettopic 的event发送到DeviceTwin,其他的event直接发送到EventHub再同步到Cloud端。

当然,该部分也包括了创建客户端,往MQTT broker发布events的接口,这里就不展开了。

  • ServiceBus

ServiceBus启动一个goroutine来接受来自beehive的消息,然后基于消息中带的参数,通过调用http client将消息通过REST-API发送到本地127.0.0.1上的目标APP。这相当于一个客户端,而APP是一个http Rest-API server,所有的操作和设备状态都需要客户端调用接口来下发和获取。

DeviceTwin

DeviceTwin包含一下几个部分的功能:

  1. 数据存储方面,将设备数据存储到本地存储sqlLite,包括三张表:devicedeviceAttrdeviceTwin
  2. 处理其他模块发送到twin module的消息,然后调用 dtc.distributeMsg来处理消息。在消息处理逻辑里面,消息被分为了四个类别,并分别发送到这四个类别的action执行处理(每一个类别又包含多个action):
    1. membership
    2. device
    3. communication
    4. twin

由于这个部分和设备更紧密相关,为何要分着几个类别,都是如何抽象,这块的理解方面还不够透彻,我们暂时只关注其主业务逻辑,官方文档对这块有比较详细的描述devicetwin

云端

入口

这里重点关注在init中加载了cloudHubcontroller(也就是edgeController)和devicecontroller三个部分。然后和edge端一样,都是beehive的套路,调用StartModules来启动所有的模块。

CloudHub

handler.WebSocketHandler.ServeEvent在websocket server上接收新边缘node的连接,然后为新node分配channel queue。再进一步将消息交给负责内容读写的逻辑处理。

channelq.NewChannelEventQueue为每一个边缘node维护了一个对应的channel queue(这里默认有10个消息的缓存),然后调用go q.dispatchMessage 来接收由controller发送到clouHub的消息,基于消息内容解析其目的node,然后将消息发送到node对应的channel排队处理。

clouHub的核心逻辑包括这两部分,读和写:

  1. 前面讲到,需要发送到边缘node的消息会发送到了node对应的channel队列上,这里通过handler.WebSocketHandler.EventWriteLoop在channel中读取到,并负责基于隧道发送处理(这里也很多判断,比如如果找不到对应的node节点,或者该node节点为offline状态等都会终止发送)。
  2. 另一方面,handler.WebSocketHandler.EventReadLoop函数从隧道上读取来自于edge端的消息,然后将消息发送到controller模块处理(如果是keepalive的心跳消息直接忽略)。

如果cloudHub往node发送消息失败,就会触发EventHandler的CancelNode操作;如果结合edgeHub端的行为的话,我们知道edgeHub会重新发起到cloud端的新连接,然后重新走一遍同步流程。

Controller(EdgeController)

controller的核心逻辑是upstream和downstream。

  • upstream
    接收由beehive发送到controller的消息,然后基于消息资源类型,通过go uc.dispatchMessage转发到不同的的goroutine处理。这里包括nodeStatus、podStatus、queryConfigMap、querySecret、queryService、queryEndpoints等;各种类别的操作都是调用k8s的client代码来将节点状态写到API-Server。

  • downstream
    通过调用k8s client代码来监听各资源的变化情况,比如对于pod是通过 dc.podManager.Events来读取消息,然后调用dc.messageLayer.Send将消息发送到edge端处理。这里也同upstream,包括pod、configmap、secret、nodes、services和endpoints这些资源。

DeviceController

deviceController同edgeController,只是其关心的资源不再是k8s的workload的子资源,而是为device定义的CRD,包括:device和deviceModel。由于主要逻辑都通edgeControler,这里不再做详细介绍。

Kubernetes 入门到实践:搭建 K3s 集群初体验 - 知乎

Excerpt

实践环境Ubuntu 22.04Docker 20.10.13K3s v1.23.13+k3s1本文将使用 Ubuntu 系统,如选择 CentOS 系统,亦可作为参考,其步骤与命令大同小异,万变不离其宗。 本文首发于:https://github.com/y0ngb1n/y0ngb1n.gith…


kubectl delete all –all

Kubernetes 基于 helm 安装 harbor_helm安装harbor-CSDN博客

Excerpt

文章浏览阅读5.7k次,点赞3次,收藏16次。所以可以简单的增加 Pod 的副本,确保组件分布到多个 Worker 节点,并利用 K8S 的 Service 机制来保证 Pod 之间的连通性。部署harbor仓库,ingress-nginx使用nodeport方式暴露自身,需要在externalURL中配置其 NodePort 端口号。浏览器访问harbor,使用节点IP+nodePort方式访问,使用默认用户名密码。复制ca.crt到docker客户端所在机器。复制ca.crt到docker客户端所在机器。推送镜像到harbor仓库。…_helm安装harbor


Kubernetes 基于 helm 安装 harbor

Harbor 的大部分组件现在都是无状态的。所以可以简单的增加 Pod 的副本,确保组件分布到多个 Worker 节点,并利用 K8S 的 Service 机制来保证 Pod 之间的连通性。
在这里插入图片描述
参考:

https://github.com/goharbor/harbor-helm

https://goharbor.io/docs/2.5.0/install-config/harbor-ha-helm/

Ingress方式暴露服务

部署openebs 持久存储

harbor默认启用了数据持久化,依赖默认存储类提供pv卷,这里使用openebs:

1
helm repo add openebs https://openebs.github.io/charts helm repo update helm install openebs openebs/openebs --namespace openebs --create-namespace

部署ingress nginx 控制器

harbor 默认使用 ingress 方式暴露服务,依赖ingress控制器,这里使用ingress-nginx:

1
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx --create-namespace \ --set controller.service.type=NodePort

获取nodeport

1
root@node01:~# kubectl -n ingress-nginx get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller NodePort 10.96.2.133 <none> 80:31523/TCP,443:31718/TCP 150m ingress-nginx-controller-admission ClusterIP 10.96.1.2 <none> 443/TCP 150m

部署 harbor 镜像仓库

添加harbor helm仓库

1
helm repo add harbor https://helm.goharbor.io

部署harbor仓库,ingress-nginx使用nodeport方式暴露自身,需要在externalURL中配置其 NodePort 端口号。

1
helm upgrade --install harbor harbor/harbor --namespace harbor --create-namespace \ --set expose.type=ingress \ --set expose.ingress.className=nginx \ --set expose.ingress.hosts.core=core.harbor.domain \ --set expose.ingress.hosts.notary=notary.harbor.domain \ --set externalURL=https://core.harbor.domain:31718 \ --set harborAdminPassword="Harbor12345"

查看harbor pods运行状态

1
root@node01:~# kubectl -n harbor get pods NAME READY STATUS RESTARTS AGE harbor-chartmuseum-787ff97489-7p45b 1/1 Running 0 4h53m harbor-core-777f5cfc9c-46vvq 1/1 Running 0 4h53m harbor-database-0 1/1 Running 0 4h53m harbor-jobservice-6d8c485bf8-s8f2k 1/1 Running 0 4h53m harbor-notary-server-5fbf9fcb58-42nhq 1/1 Running 1 (4h52m ago) 4h53m harbor-notary-signer-5894f4c77c-tn55p 1/1 Running 1 (4h52m ago) 4h53m harbor-portal-685498cc69-tk4b7 1/1 Running 0 4h53m harbor-redis-0 1/1 Running 0 4h53m harbor-registry-d9bb75d7b-8pvql 2/2 Running 0 4h53m harbor-trivy-0 1/1 Running 0 4h53m

查看pvc

1
root@node01:~# kubectl -n harbor get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE data-harbor-redis-0 Bound pvc-f9839aa4-89ed-4971-92ae-047b271e6205 1Gi RWO local-hostpath 17m data-harbor-trivy-0 Bound pvc-4b25125b-0fb8-40ed-8d3b-80c70f90cc5a 5Gi RWO local-hostpath 17m database-data-harbor-database-0 Bound pvc-7d826b47-066e-4da6-8fb8-734be3823667 1Gi RWO local-hostpath 17m harbor-chartmuseum Bound pvc-432582f3-62f8-49da-96d0-37a01e015c57 5Gi RWO local-hostpath 17m harbor-jobservice Bound pvc-080863a2-277d-4293-93ec-dea149b051ec 1Gi RWO local-hostpath 17m harbor-registry Bound pvc-906d49c6-0fd8-435d-84f1-46b6e7132802 5Gi RWO local-hostpath 17m

查看ingress

1
root@node01:~# kubectl -n harbor get ingress NAME CLASS HOSTS ADDRESS PORTS AGE harbor-ingress <none> core.harbor.domain 10.96.2.133 80, 443 4h53m harbor-ingress-notary <none> notary.harbor.domain 10.96.2.133 80, 443 4h53m

浏览器访问harbor管理界面,如果没有DNS解析,注意将192.168.72.50 core.harbor.domain 加入本地hosts文件中,其中192.168.72.50 为kubernetes集群任意节点IP地址。

1
https://core.harbor.domain:31718/

默认用户名密码为admin/Harbor12345

在这里插入图片描述

集群外docker客户端验证上传镜像。

首先导出ca.crt证书

1
kubectl -n harbor get secrets harbor-ingress -o jsonpath="{.data.ca\.crt}" | base64 -d >ca.crt

复制ca.crt到docker客户端所在机器

1
root@ubuntu:~# mkdir -p /etc/docker/certs.d/core.harbor.domain:31718/ root@ubuntu:~# ls /etc/docker/certs.d/core.harbor.domain:31718/ ca.crt

如果使用containerd,配置类似:

1
root@ubuntu:~# mkdir -p /etc/containerd/certs.d/core.harbor.domain:31718/ root@ubuntu:~# ls /etc/containerd/certs.d/core.harbor.domain:31718/ ca.crt

配置hosts解析

1
echo "192.168.72.50 core.harbor.domain" >>/etc/hosts

登录harbor仓库

1
root@ubuntu:~# docker login -u admin -p Harbor12345 https://core.harbor.domain:31718 WARNING! Using --password via the CLI is insecure. Use --password-stdin. WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See Login Succeeded

推送镜像到harbor仓库

1
root@ubuntu:~# docker tag centos:latest core.harbor.domain:31718/library/centos:latest root@ubuntu:~# docker push core.harbor.domain:31718/library/centos:latest The push refers to repository [core.harbor.domain:31718/library/centos] 74ddd0ec08fa: Pushed latest: digest: sha256:a1801b843b1bfaf77c501e7a6d3f709401a1e0c83863037fa3aab063a7fdb9dc size: 529

NodePort方式暴露服务

harbor 使用自身的nodePort方式暴露服务,无需部署和依赖 ingress-nginx 控制器:

1
export node_ip=192.168.72.50 helm upgrade --install harbor harbor/harbor --namespace harbor --create-namespace \ --set expose.type=nodePort \ --set expose.tls.auto.commonName=$node_ip \ --set externalURL='https://$node_ip:30003'

说明:其中 192.168.72.50 为kubernetes集群任一节点IP地址。

查看service,确认service harbor 的 TYPE 为 NodePort

1
$ kubectl -n harbor get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE harbor NodePort 10.96.1.217 <none> 80:30002/TCP,443:30003/TCP,4443:30004/TCP 100s harbor-chartmuseum ClusterIP 10.96.0.116 <none> 80/TCP 100s harbor-core ClusterIP 10.96.0.125 <none> 80/TCP 100s harbor-database ClusterIP 10.96.1.189 <none> 5432/TCP 100s harbor-jobservice ClusterIP 10.96.1.99 <none> 80/TCP 100s harbor-notary-server ClusterIP 10.96.0.5 <none> 4443/TCP 100s harbor-notary-signer ClusterIP 10.96.0.164 <none> 7899/TCP 100s harbor-portal ClusterIP 10.96.1.25 <none> 80/TCP 100s harbor-redis ClusterIP 10.96.0.224 <none> 6379/TCP 100s harbor-registry ClusterIP 10.96.3.233 <none> 5000/TCP,8080/TCP 100s harbor-trivy ClusterIP 10.96.2.193 <none> 8080/TCP 100s

浏览器访问harbor,使用节点IP+nodePort方式访问,使用默认用户名密码admin/Harbor12345进行登录:

1
https://192.168.72.50:30003/

登录后如下:

在这里插入图片描述

docker客户端配置。

首先导出ca.crt证书

1
kubectl -n harbor get secrets harbor-nginx -o jsonpath="{.data.ca\.crt}" | base64 -d >ca.crt

复制ca.crt到docker客户端所在机器

1
root@ubuntu:~# mkdir -p /etc/docker/certs.d/192.168.72.50:30003/ root@ubuntu:~# ls /etc/docker/certs.d/192.168.72.50:30003/ ca.crt

登录harbor仓库

1
root@ubuntu:~# docker login -u admin -p Harbor12345 https://192.168.72.50:30003 WARNING! Using --password via the CLI is insecure. Use --password-stdin. WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See Login Succeeded

推送镜像到harbor仓库

1
root@ubuntu:~# docker tag centos:latest 192.168.72.50:30003/library/centos:latest root@ubuntu:~# docker push 192.168.72.50:30003/library/centos:latest The push refers to repository [192.168.72.50:30003/library/centos] 74ddd0ec08fa: Pushed latest: digest: sha256:a1801b843b1bfaf77c501e7a6d3f709401a1e0c83863037fa3aab063a7fdb9dc size: 529

【时间序列聚类】KMedoids聚类+DTW算法_soft dtw-based k medoids-CSDN博客

Excerpt

文章浏览阅读1.6w次,点赞30次,收藏182次。前言KMedoids的聚类有时比KMeans的聚类效果要好。手上正好有一批时序数据,今天用KMedoids试下聚类效果安装KMedoids可以使用sklearn的拓展聚类模块scikit-learn-extra,模块需要保证Python (>=3.6)scikit-learn(>=0.22)安装 scikit-learn-extraPyPi: pip install scikit-learn-extraConda: conda install -c _soft dtw-based k medoids


前言

KMedoids的聚类有时比KMeans的聚类效果要好。手上正好有一批时序数据,今天用KMedoids试下聚类效果

安装

KMedoids可以使用sklearn的拓展聚类模块scikit-learn-extra,模块需要保证

安装 scikit-learn-extra

1
2
3
4
5
PyPi: pip install scikit-learn-extra

Conda: conda install -c conda-forge scikit-learn-extra

Git: pip install https://github.com/scikit-learn-contrib/scikit-learn-extra/archive/master.zip

安装 tslearn

1
2
3
4
5
PyPi: python -m pip install tslearn

Conda: conda install -c conda-forge tslearn

Git: python -m pip install https://github.com/tslearn-team/tslearn/archive/master.zip

为什么要使用scikit-learn-extra和tslearn两个模块?因为sklearn里面没有自带的KMedoids,也没有类似DTW的时序度量算法,但组合两者恰好能解决问题

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import numpy as np

from sklearn_extra.cluster import KMedoids

import tslearn.metrics as metrics

import data_process

from tslearn.clustering import silhouette_score

from tslearn.preprocessing import TimeSeriesScalerMeanVariance

from tslearn.generators import random_walks

from sklearn.preprocessing import StandardScaler

import matplotlib.pyplot as plt

X = np.loadtxt("top100.txt",dtype=np.float,delimiter=",")

X = data_process.downsample(X,30)

seed = 0

def test_elbow():

global X,seed

distortions = []

dists = metrics.cdist_dtw(X)

for i in range ( 2 , 15 ):

km = KMedoids(n_clusters=i,random_state=seed,metric="precomputed")

km.fit(dists)

distortions.append(km.inertia_)

plt.plot(range ( 2 , 15 ), distortions, marker= 'o' )

plt.xlabel( 'Number of clusters' )

plt.ylabel( 'Distortion' )

plt.show()

def test_kmedoids():

num_cluster = 5

km = KMedoids(n_clusters= num_cluster, random_state=0,metric="precomputed")

dists = metrics.cdist_dtw(X)

y_pred = km.fit_predict(dists)

np.fill_diagonal(dists,0)

score = silhouette_score(dists,y_pred,metric="precomputed")

print(X.shape)

print(y_pred.shape)

print("silhouette_score: " + str(score))

for yi in range(num_cluster):

plt.subplot(3, 2, yi + 1)

for xx in X[y_pred == yi]:

plt.plot(xx.ravel(), "k-", alpha=.3)

plt.plot(X[km.medoid_indices_[yi]], "r-")

plt.text(0.55, 0.85,'Cluster %d' % (yi + 1),

transform=plt.gca().transAxes)

if yi == 1:

plt.title("KMedoids" + " + DBA-DTW")

plt.tight_layout()

plt.show()

test_kmedoids()

采用KMedoids + DBA-DTW聚类效果  # 轮廓系数silhouette_score: 0.5465097470777784

采用KMedoids + SoftDTW聚类效果   # 轮廓系数silhouette_score: 0.6528261125440392

直接采用欧氏距离的聚类效果    # 轮廓系数silhouette_score: 0.5209641775604567

相比采用KMeans,KMedoids在的聚类中心(红线部分)从视觉上要似乎要更好(KMeans+DTW聚类效果可见该文章最后一节),但轮廓系数却不如KMeans。但仅欧氏距离而言,KMedoids的轮廓系数要比KMeans更好那么一点

关于这个系列教程,我是从最基础的开发环境搭建到项目进阶到后面的项目开发这个过程来写的。我一直秉承从实际项目开发以及源码解析的角度去写好这个教程,并让从未接触过编程的朋友能学好kotlin这门语言。所以我想把这个教程写到最详细、最全面。并且也持之以恒的写下去。

前言

在坚持的这个过程中,接触到了Kotlin的魅力,也碰到了困难,同时也认识许多想学好kotlin的朋友。这是我最开心的地方,也是我坚持下来的动力所在。我的原意是把这个系列的每一篇文章完成之后,再写一篇文章对这个教程做出总结,但是很多朋友都说我的这个教程没有一个很好的顺序,看的断断续续的。故而我决定提前把这篇文章写出来。可以说是这个教程的大纲。同时也能让大家更好且更系统的去学习Kotlin,这个教程我个人认为是比官网的教程写的详细、全面的,这个教程我也会持续的更新。

这个系列教程的我放在了Github上面,里面涵盖了我所有文章中例子的源代码。

传送门:KotlinLearn

写这个系列教程的初衷

本人作为一个Android开发者,在Kotlin语言正式出现在人们面前的时候,或多或少的接触到了这门语言,并
自学了一部分,当谷歌宣布Kotlin成为Android开发的标准语言,才有打算用心的去学习这门语言,并能用于实际的开
发当中。在我学习的过程中,学的越深入越被Kotlin简洁的代码,良好的代码阅读性深深的折服。但是在官方的文档中,写
的很不想细,有些代码甚至看不懂其实现,基础性的东西几乎不存在。如果你没有一定的编程经验我相信你也是看不懂的。

但是请你不要放弃对这门语言的学习,我也是一点一点学习过来的,你在学习过程中遇到的坑,可能我也遇到过。我非常欢迎各位
一起学习,一起探索。共同的进步。

所以,我会根据自身的编程经验、技术。写完整个Kotlin系列教程。整个教程由浅如深,即使你没有编程经验你也能看懂,教程中会介绍Koltin的特性,以及其和Java的差异性。让更多的朋友爱上这门语言。

开撸

让我们忘记生活的烦恼、放下沉重的枷锁、沉浸在代码的世界中,打开音乐的分贝,迈开脚步开始学习Kotlin大法吧。

学习架构

我把这个教程命名为:从无到有系列,这个系列目前还没写完,故而下面的链接有一些是没有实际内容的,我会在后面持续的补齐,这里只是实现了整个教程的大纲。

整个项目根据学习的难以程度以及学习路线我分为了五个模块。我会按照的学习架构去排序。大家可以按照这个顺序去学习…

引言

初级篇

对于入门篇的内容不多,主要是讲解了Kotlin中的基础语法,涵盖了开发环境搭建变量常量注释数据类型控制语句操作符重载空安全基本函数字符串处理

中级篇

Kotlin是一门面向对象的开发语言。既然是面向对象,则在中级篇主要讲解其三大特性,以及类成员数据类抽象类接口类内部类继承类等关于Kotlin面向对象的方方面面的讲解

高级篇

高级篇的内容要多一些,只要是对Kotlin中的高级用法以及一些独特的东西。涵盖了lambda表达式高阶函数集合泛型扩展委托异常处理注解反射协程

进阶篇

进阶篇主要讲解的是在实际开发项目中的注意事项及一些高级操作,例如Kotlin中的设计模式编码规范

实战篇

实战篇主要向大家介绍使用Kotlin开发Android项目时,介绍一些常用和Kotlin一起开发的库,以及对他们的使用及讲解。
比如说AnkoDagger2RxKotlin、或者用Kotlin编写的一些自定义组件等等…

其他

在这里我意思整合了一些关于Kotlin的学习资源,以供大家学习。

官网

视频教程

书籍

关于书籍的分享,这里有着官网的中英文PDF文档,Kotlin极简教程、Kotlin程序开发入门经验等等书籍。这些我都分享在了我的Kotlin交流群里面。这里就不一一的分享了。

更多

开源项目

最后

关于此教程中的实例源码都在项目中,有兴趣的可以去看看。当然也希望您不吝的给个关注或star,同时也希望您指出这个教程的不足指出。因为您的关注是我坚持的动力,也让更多的朋友与Kotlin爱好者能更好的去学习它。

传送门:KotlinLearn

我相信在你学完整个系列教程之后,一定是可以用Kotlin进行实际项目开发的。近期我也会用Koltin语言去编写我自己的Android项目,也会用Kotlin去实现后端用于我APP中的接口提供。
如果你在学习过程中遇到任何的问题,不管你是写后端Android以及前端的开发者都可以联系我,或者对于Android开发很有兴趣。都可以在项目Issuse我,或者加入在下的Koltin交流群一起学习、研究。

_我的个人博客_:Jetictors
GithubJteictors
掘金Jteictors

欢迎各位大佬进群共同研究、探索

QQ群号:497071402