k8s资源限制、多账号案例、网络实现方式

1. 资源管理

当你定义 Pod 时可以选择性地为每个 容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存(RAM)大小;此外还有其他类型的资源。

当你为 Pod 中的 Container 指定了资源 请求 时,调度器就利用该信息决定将 Pod 调度到哪个节点上。 当你还为 Container 指定了资源 限制 时,kubelet 就可以确保运行的容器不会使用超出所设约束的资源。 kubelet 还会为容器预留所 请求 数量的系统资源,供其使用。

在 Kubernetes 体系中,资源默认是被多租户共享使用的,租户间不可避免地存在资源竞争问题。为了满足不同租户多样的服务质量需求,集群管理员需要为租户设置非常精细的资源配额以及资源限制。截止 1.14 版本,Kubernetes 已经支持分别从 Namespace、Pod 和 Container 三个级别对资源进行管理。

1.1 请求和限制

如果 Pod 运行所在的节点具有足够的可用资源,容器可能(且可以)使用超出对应资源 request 属性所设置的资源量。不过,容器不可以使用超出其资源 limit 属性所设置的资源量。

例如,如果你将容器的 memory 的请求量设置为 256 MiB,而该容器所处的 Pod 被调度到一个具有 8 GiB 内存的节点上,并且该节点上没有其他 Pods 运行,那么该容器就可以尝试使用更多的内存。

如果你将某容器的 memory 限制设置为 4 GiB,kubelet (和 容器运行时) 就会确保该约束生效。 容器运行时会禁止容器使用超出所设置资源约束的资源。 例如:当容器中进程尝试使用超出所允许内存量的资源时,系统内核会将尝试申请内存的进程终止, 并引发内存不足(OOM)错误。

约束值可以以被动方式来实现(系统会在发现违例时进行干预),或者通过强制生效的方式实现 (系统会避免容器用量超出约束值)。不同的容器运行时采用不同方式来实现相同的限制。

如果某 Container 设置了自己的内存限制但未设置内存请求,Kubernetes 自动为其设置与内存限制相匹配的请求值。类似的,如果某 Container 设置了 CPU 限制值但未设置 CPU 请求值,则 Kubernetes 自动为其设置 CPU 请求 并使之与 CPU 限制值匹配。

1.2 资源类型

CPU内存都是资源类型。每种资源类型具有其基本单位。 CPU 表达的是计算处理能力,其单位是 Kubernetes CPUs。 内存的单位是字节。 如果你使用的是 Kubernetes v1.14 或更高版本,则可以指定巨页(Huge Page)资源。 巨页是 Linux 特有的功能,节点内核在其中分配的内存块比默认页大小大得多。

例如,在默认页面大小为 4KiB 的系统上,你可以指定约束 hugepages-2Mi: 80Mi。 如果容器尝试分配 40 个 2MiB 大小的巨页(总共 80 MiB ),则分配请求会失败。

你不能过量使用 hugepages- * 资源。 这与 memory 和 cpu 资源不同。

CPU 和内存统称为计算资源,或简称为资源。 计算资源的数量是可测量的,可以被请求、被分配、被消耗。 它们与 API 资源 不同。 API 资源(如 Pod 和 Service)是可通过 Kubernetes API 服务器读取和修改的对象。

1.3 Pod 和 容器的资源请求和限制

Pod 中的每个容器都可以指定以下的一个或者多个值:

  • spec.containers[].resources.limits.cpu
  • spec.containers[].resources.limits.memory
  • spec.containers[].resources.limits.hugepages-<size>
  • spec.containers[].resources.requests.cpu
  • spec.containers[].resources.requests.memory
  • spec.containers[].resources.requests.hugepages-<size>

CPU 的含义
CPU资源以毫秒定义。 如果您的容器需要运行两个完整的核心,那么您将设置值2000m。 如果您的容器只需要1/4的核心,那么您将设置一个250m的值。

关于CPU请求要记住的一件事是,如果您输入的值大于最大节点的核心数,则永远不会调度您的Pod。 假设您有一个需要四个核心的Pod,但您的Kubernetes群集由双核VM组成——您的Pod将永远不会被调度!

除非您的应用程序专门用于利用多个核心(科学计算和某些数据库),否则通常最好将CPU请求保持在1或更低,并运行更多副本以扩展它。 这为系统提供了更大的灵活性和可靠性。

就CPU限制而言,事情其实很有趣。 CPU被认为是可压缩资源。 如果您的应用程序开始达到您的CPU限制,Kubernetes会开始限制您的容器。 这意味着CPU将受到人为限制,使您的应用程序性能可能更差! 但是,它不会被终止或退出。 您可以使用Liveness探针的运行状况检查来确保性能未受影响。

内存的含义
内存资源以字节为单位定义。 通常,你给内存一个mebibyte值(这基本上与兆字节相同),实际上你可以提供从字节到PB的任何单位。

和CPU一样,如果您输入的内存请求大于节点上的内存量,则你的Pod永远不会被调度。

与CPU资源不同,内存无法压缩。 因为没有办法限制内存使用量,如果容器超过其内存限制,它将被终止。 如果您的Pod由DeploymentStatefulSetDaemonSet或其他类型的控制器管理,则控制器会轮转替换。

举例:

root@k8s-ansible-client:~/yaml/20211031/limit-case# cat mem-and-cpu-limit.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: limit-test-deployment
  namespace: pop
spec:
  replicas: 1
  selector:
    matchLabels: #rs or deployment
      app: limit-test-pod
  template:
    metadata:
      labels:
        app: limit-test-pod
    spec:
      containers:
      - name: limit-test-container
        image: lorel/docker-stress-ng
        resources:
          limits:
            cpu: "500m"
            memory: "300Mi"
          requests:
            memory: "100Mi"
            cpu: "250m"
        args: ["--vm", "2", "--vm-bytes", "256M"]

root@k8s-ansible-client:~/yaml/20211031/limit-case# kubectl apply -f mem-and-cpu-limit.yaml 
deployment.apps/limit-test-deployment created

root@k8s-ansible-client:~/yaml/20211031/limit-case# kubectl get pods,deploy -n pop
NAME                                         READY   STATUS    RESTARTS   AGE
pod/limit-test-deployment-5574ccb468-ld9pt   1/1     Running   0          4s

NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/limit-test-deployment   1/1     1            1           4s

# 这里限制了cpu最高500m,内存300Mi; cpu已经到达上限,内存需要过一会(但是不会超过300Mi以上)
root@k8s-ansible-client:~/yaml/20211031/limit-case# kubectl top pod limit-test-deployment-5574ccb468-ld9pt -n pop
W1113 22:49:28.607818  211109 top_pod.go:140] Using json format to get metrics. Next release will switch to protocol-buffers, switch early by passing --use-protocol-buffers flag
NAME                                     CPU(cores)   MEMORY(bytes)   
limit-test-deployment-5574ccb468-ld9pt   501m         247Mi 

排查错误
启动容器之后,使用describe排查不出来报错信息的时候,使用下面方式:

root@k8s-ansible-client:~/yaml/20211031/limit-case# kubectl get deploy limit-test-deployment -n pop -o json

1.3.1 LimitRange

默认情况下, Kubernetes 集群上的容器运行使用的计算资源没有限制。 使用资源配额,集群管理员可以以名字空间为单位,限制其资源的使用与创建。 在命名空间中,一个 Pod 或 Container 最多能够使用命名空间的资源配额所定义的 CPU 和内存用量。 有人担心,一个 Pod 或 Container 会垄断所有可用的资源。 LimitRange 是在命名空间内限制资源分配(给多个 Pod 或 Container)的策略对象。

一个 LimitRange(限制范围) 对象提供的限制能够做到:

  • 在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。
  • 在一个命名空间中实施对每个 PersistentVolumeClaim 能申请的最小和最大的存储空间大小的限制。
  • 在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。
  • 设置一个命名空间中对计算资源的默认申请/限制值,并且自动的在运行时注入到多个 Container 中
root@k8s-ansible-client:~/yaml/20211031/limit-case# cat test-limitrange.yaml 
apiVersion: v1
kind: LimitRange
metadata:
  name: limitrange-magedu
  namespace: pop
spec:
  limits:
  - type: Container       #限制的资源类型
    max:
      cpu: "2"            #限制单个容器的最大CPU
      memory: "2Gi"       #限制单个容器的最大内存
    min:
      cpu: "500m"         #限制单个容器的最小CPU
      memory: "512Mi"     #限制单个容器的最小内存
    default:
      cpu: "500m"         #默认单个容器的CPU限制
      memory: "512Mi"     #默认单个容器的内存限制
    defaultRequest:
      cpu: "500m"         #默认单个容器的CPU创建请求
      memory: "512Mi"     #默认单个容器的内存创建请求
    maxLimitRequestRatio:
      cpu: 2              #限制CPU limit/request比值最大为2  
      memory: 2         #限制内存limit/request比值最大为1.5
  - type: Pod
    max:
      cpu: "4"            #限制单个Pod的最大CPU
      memory: "4Gi"       #限制单个Pod最大内存
  - type: PersistentVolumeClaim
    max:
      storage: 50Gi        #限制PVC最大的requests.storage
    min:
      storage: 30Gi        #限制PVC最小的requests.storage

启用LimitRange

root@k8s-ansible-client:~/yaml/20211031/limit-case# kubectl apply -f test-limitrange.yaml 
limitrange/limitrange-magedu created
root@k8s-ansible-client:~/yaml/20211031/limit-case# kubectl describe limitranges limit -n pop
Name:                  limitrange-magedu
Namespace:             pop
Type                   Resource  Min    Max   Default Request  Default Limit  Max Limit/Request Ratio
----                   --------  ---    ---   ---------------  -------------  -----------------------
Container              memory    512Mi  2Gi   512Mi            512Mi          2
Container              cpu       500m   2     500m             500m           2
Pod                    cpu       -      4     -                -              -
Pod                    memory    -      4Gi   -                -              -
PersistentVolumeClaim  storage   30Gi   50Gi  -                -              -

1.3.2 ResourceRequests/ResourceLimits 设置 Container 资源需求和上限

在 Container 级别可以对两种计算资源进行管理:CPU 和内存。ResourceRequests 表示容器希望被分配到的可完全保证的资源量,Requests 的值会被提供给 Kubernetes 调度器,以便优化基于资源请求的容器调度;ResourceLimits 表示容器能用的资源上限,这个上限的值会影响在节点上发生资源竞争时的解决策略。

yaml文件里面,加上以下参数:

resources:
   limits:
     cpu: 500m
     memory: 1Gi
   requests:
     cpu: 10m
     memory: 4Mi

1.4 Namespace 资源的请求和限制

在 Namespace 级别,可以通过创建 ResourceQuota 对象提供一个总体资源使用量限制:一方面可以设置该命名空间中 Pod 可以使用到的计算资源(CPU、内存)、存储资源总量上限;另一方面可以限制该 Namespace 中某种类型对象(如 Pod、RC、Service、Secret、ConfigMap、PVC 等)的总量上限。

root@k8s-ansible-client:~/yaml/20211031/limit-case# cat test-resourcequota-pop.yaml 
apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-magedu
  namespace: pop
spec:
  hard:
    requests.cpu: "46"
    limits.cpu: "46"
    requests.memory: 120Gi
    limits.memory: 120Gi
    requests.nvidia.com/gpu: 4
    pods: "20"
    services: "20"

**启用ResourceQuota **

root@k8s-ansible-client:~/yaml/20211031/limit-case# kubectl apply -f test-resourcequota-pop.yaml 
resourcequota/quota-magedu created
root@k8s-ansible-client:~/yaml/20211031/limit-case# kubectl describe resourcequotas -n pop
Name:                    quota-magedu
Namespace:               pop
Resource                 Used  Hard
--------                 ----  ----
limits.cpu               0     46
limits.memory            0     120Gi
pods                     0     20
requests.cpu             0     46
requests.memory          0     120Gi
requests.nvidia.com/gpu  0     4
services                 2     20

2. k8s 多账户实现案例

2.1 鉴权简介

在 k8s 中,你必须在鉴权(授予访问权限)之前进行身份验证(登录),有关身份验证的信息, 请参阅访问控制概述.

k8s 期望请求中存在 REST API 常见的属性。 这意味着 k8s 鉴权适用于现有的组织范围或云提供商范围的访问控制系统, 除了 k8s API 之外,它还可以处理其他 API。

k8s 使用 API 服务器对 API 请求进行鉴权。 它根据所有策略评估所有请求属性来决定允许或拒绝请求。 一个 API 请求的所有部分都必须被某些策略允许才能继续。 这意味着默认情况下拒绝权限。

(尽管 k8s 使用 API 服务器,但是依赖于特定对象种类的特定字段的访问控制 和策略由准入控制器处理。)

当系统配置了多个鉴权模块时,k8s 将按顺序使用每个模块。 如果任何鉴权模块批准或拒绝请求,则立即返回该决定,并且不会与其他鉴权模块协商。 如果所有模块对请求没有意见,则拒绝该请求。 被拒绝响应返回 HTTP 状态代码 403

2.2 k8s API请求属性

  • 用户 - 身份验证期间提供的 user 字符串。
  • - 经过身份验证的用户所属的组名列表。
  • 额外信息 - 由身份验证层提供的任意字符串键到字符串值的映射。
  • API - 指示请求是否针对 API 资源。
  • 请求路径 - 各种非资源端点的路径,如 /api/healthz
  • API 请求动词 - API 动词 getlistcreateupdatepatchwatchproxyredirectdeletedeletecollection 用于资源请求。 要确定资源 API 端点的请求动词,请参阅 确定请求动词
  • HTTP 请求动词 - HTTP 动词 getpostputdelete 用于非资源请求。
  • Resource - 正在访问的资源的 ID 或名称(仅限资源请求)- 对于使用 getupdatepatchdelete 动词的资源请求,你必须提供资源名称。
  • 子资源 - 正在访问的子资源(仅限资源请求)。
  • 名字空间 - 正在访问的对象的名称空间(仅适用于名字空间资源请求)。
  • API 组 - 正在访问的 API 组 (仅限资源请求)。空字符串表示核心 API 组

2.3 请求动作

非资源请求

对于 /api/v1/.../apis/<group>/<version>/... 之外的端点的请求被 视为“非资源请求(Non-Resource Requests)”,并使用该请求的 HTTP 方法的 小写形式作为其请求动词。 例如,对 /api/healthz 这类端点的 GET 请求将使用 get 作为其动词。

资源请求

要确定对资源 API 端点的请求动词,需要查看所使用的 HTTP 动词以及该请求是针对 单个资源还是一组资源:

HTTP 动词 请求动词
POST create
GET, HEAD get (针对单个资源)、list(针对集合)
PUT update
PATCH patch
DELETE delete(针对单个资源)、deletecollection(针对集合)

Kubernetes 有时使用专门的动词以对额外的权限进行鉴权。例如:

  • PodSecurityPolicy
    • policy API 组中 podsecuritypolicies 资源使用 use 动词
  • RBAC
    • rbac.authorization.k8s.io API 组中 rolesclusterroles 资源的 bindescalate 动词
  • 身份认证
    • 对核心 API 组中 usersgroupsserviceaccounts 以及 authentication.k8s.io API 组中的 userextras 所使用的 impersonate 动词

2.4 鉴定模块

  • Node - 一个专用鉴权组件,根据调度到 kubelet 上运行的 Pod 为 kubelet 授予权限。 了解有关使用节点鉴权模式的更多信息,请参阅节点鉴权
  • ABAC - 基于属性的访问控制(ABAC)定义了一种访问控制范型,通过使用将属性组合 在一起的策略,将访问权限授予用户。策略可以使用任何类型的属性(用户属性、资源属性、 对象,环境属性等)。要了解有关使用 ABAC 模式的更多信息,请参阅ABAC 模式
  • RBAC - 基于角色的访问控制(RBAC)是一种基于企业内个人用户的角色来管理对 计算机或网络资源的访问的方法。在此上下文中,权限是单个用户执行特定任务的能力, 例如查看、创建或修改文件。要了解有关使用 RBAC 模式的更多信息,请参阅 RBAC 模式
    • 被启用之后,RBAC(基于角色的访问控制)使用 rbac.authorization.k8s.io API 组来 驱动鉴权决策,从而允许管理员通过 Kubernetes API 动态配置权限策略。
    • 要启用 RBAC,请使用 --authorization-mode = RBAC 启动 API 服务器。
  • Webhook - WebHook 是一个 HTTP 回调:发生某些事情时调用的 HTTP POST; 通过 HTTP POST 进行简单的事件通知。实现 WebHook 的 Web 应用程序会在发生某些事情时 将消息发布到 URL。要了解有关使用 Webhook 模式的更多信息,请参阅Webhook 模式

2.5 使用RBAC鉴权

基于角色(Role)的访问控制(RBAC)是一种基于组织中用户的角色来调节控制对 计算机或网络资源的访问的方法。

RBAC 鉴权机制使用 rbac.authorization.k8s.io API 组 来驱动鉴权决定,允许你通过 Kubernetes API 动态配置策略。

要启用 RBAC,在启动 API 服务器 时将 --authorization-mode 参数设置为一个逗号分隔的列表并确保其中包含 RBAC

kube-apiserver --authorization-mode=Example,RBAC --<其他选项> --<其他选项>

2.5.1 API对象

RBAC API 声明了四种 Kubernetes 对象:Role、ClusterRole、RoleBinding 和 ClusterRoleBinding

Role 和 ClusterRole

RBAC 的 RoleClusterRole 中包含一组代表相关权限的规则。 这些权限是纯粹累加的(不存在拒绝某操作的规则)。

Role 总是用来在某个名字空间 内设置访问权限;在你创建 Role 时,你必须指定该 Role 所属的名字空间。

与之相对,ClusterRole 则是一个集群作用域的资源。这两种资源的名字不同(Role 和 ClusterRole)是因为 Kubernetes 对象要么是名字空间作用域的,要么是集群作用域的, 不可两者兼具。

ClusterRole 有若干用法。你可以用它来:

  1. 定义对某名字空间域对象的访问权限,并将在各个名字空间内完成授权;
  2. 为名字空间作用域的对象设置访问权限,并跨所有名字空间执行授权;
  3. 为集群作用域的资源定义访问权限。

RoleBinding 和 ClusterRoleBinding

角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户。 它包含若干 主体(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。 RoleBinding 在指定的名字空间中执行授权,而 ClusterRoleBinding 在集群范围执行授权。

一个 RoleBinding 可以引用同一的名字空间中的任何 Role。 或者,一个 RoleBinding 可以引用某 ClusterRole 并将该 ClusterRole 绑定到 RoleBinding 所在的名字空间。 如果你希望将某 ClusterRole 绑定到集群中所有名字空间,你要使用 ClusterRoleBinding。

RoleBinding 或 ClusterRoleBinding 对象的名称必须是合法的 路径区段名称

2.5.2 命令空间指定帐号授权

在指定namespace创建账户

root@k8s-ansible-client:~/yaml/20211031/limit-case# kubectl create serviceaccount pop user1 -n  pop
serviceaccount/pop created

创建role规则:

root@k8s-ansible-client:~/yaml/20211031/role# cat pop-role.yaml
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: pop
  name: user1-role
rules:
- apiGroups: ["*"]
  resources: ["pods/exec"]
  #verbs: ["*"]
  ##RO-Role
  verbs: ["get", "list", "watch", "create"]

- apiGroups: ["*"]
  resources: ["pods"]
  #verbs: ["*"]
  ##RO-Role
  verbs: ["get", "list", "watch"]

- apiGroups: ["apps/v1"]
  resources: ["deployments"]
  #verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  ##RO-Role
  verbs: ["get", "watch", "list"]

root@k8s-ansible-client:~/yaml/20211031/role# kubectl apply  -f pop-role.yaml 
role.rbac.authorization.k8s.io/user1-role created

root@k8s-ansible-client:~/yaml/20211031/role# kubectl get roles.rbac.authorization.k8s.io -n pop
NAME         CREATED AT
user1-role   2021-11-13T15:29:22Z
root@k8s-ansible-client:~/yaml/20211031/role# kubectl describe roles.rbac.authorization.k8s.io -n pop
Name:         user1-role
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources            Non-Resource URLs  Resource Names  Verbs
  ---------            -----------------  --------------  -----
  pods.*/exec          []                 []              [get list watch create]
  pods.*               []                 []              [get list watch]
  deployments.apps/v1  []                 []              [get watch list]

将规则与账户进行绑定

root@k8s-ansible-client:~/yaml/20211031/role# cat pop-role-bind.yaml 
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: role-bind-pop
  namespace: pop
subjects:
- kind: ServiceAccount
  name: pop
  namespace: pop
roleRef:
  kind: Role
  name: user1-role
  apiGroup: rbac.authorization.k8s.io

root@k8s-ansible-client:~/yaml/20211031/role# kubectl apply -f pop-role-bind.yaml 
rolebinding.rbac.authorization.k8s.io/role-bind-pop created

root@k8s-ansible-client:~/yaml/20211031/role# kubectl describe rolebindings.rbac.authorization.k8s.io -n pop
Name:         role-bind-pop
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  Role
  Name:  user1-role
Subjects:
  Kind            Name  Namespace
  ----            ----  ---------
  ServiceAccount  pop   pop

获取token

root@k8s-ansible-client:~/yaml/20211031/role# kubectl get secrets -n pop
NAME                  TYPE                                  DATA   AGE
default-token-4nrw2   kubernetes.io/service-account-token   3      24d
pop-token-spgzj       kubernetes.io/service-account-token   3      50m
test-tls-secret       Opaque                                2      14d
user1-token-7dsj9     kubernetes.io/service-account-token   3      14m
www-tls-secret        Opaque                                2      14d

root@k8s-ansible-client:~/yaml/20211031/role# kubectl describe secrets pop-token-spgzj -n pop
Name:         pop-token-spgzj
Namespace:    pop
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: pop
              kubernetes.io/service-account.uid: 4125ee58-e8cd-4d6a-b01d-9af9049edcb8

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1350 bytes
namespace:  3 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IlA3YjBkSVE3QWlJdzRNOVlfcGpHWWI3dTU3OUhtczZTVGJldk91TS1pejQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJwb3AiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoicG9wLXRva2VuLXNwZ3pqIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6InBvcCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQxMjVlZTU4LWU4Y2QtNGQ2YS1iMDFkLTlhZjkwNDllZGNiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpwb3A6cG9wIn0.MAZrhLoxXzm5CcGaRoSnJmXXVCKTins1wijOFE0OMLKSJO5sUQigd0w55veZS--1rrbLqOBENhQ7Mr5ULPBpH9ARR7TCSby1syG-fFRoWdRXuGCgA6Sy79LsWIFtiE0e93bFS7LfpfsdBdbDzDlccKxLmYfiCLjGqc60CJNWSP9W5woYIZpcOPGNnTa14cBQ44tN5hMQS5o0qGDfM05uXx_Q_mnyk8kbieJnrmNVFn5-9EiX8ELryaJ_onDKxLuILH8aoE2IE0oTuDuqAC178wilFbV2-2v38x4LEYW0s3AFO9T9x62kSdBh2I94dUb5Ai3AthwlEK9J7sVHrTJ0sA


登录dashboard验证

image.png

2.5.3 使用kube-config文件登录dashboard

创建csr文件

root@k8s-ansible-client:~/yaml/20211031/role# cat pop-csr.json 
{
  "CN": "China",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}

自签证书

# 从mater把ssl里面的证书cp过来
root@k8s-ansible-client:~/yaml/20211031/role# cfssl gencert -ca=/etc/kubernetes/ssl/ca.pem  -ca-key=/etc/kubernetes/ssl/ca-key.pem -config=/etc/kubeasz/clusters/k8s-pop/ssl/ca-config.json -profile=kubernetes pop-csr.json | cfssljson -bare  pop
2021/11/14 00:47:41 [INFO] generate received request
2021/11/14 00:47:41 [INFO] received CSR
2021/11/14 00:47:41 [INFO] generating key: rsa-2048
2021/11/14 00:47:42 [INFO] encoded CSR
2021/11/14 00:47:42 [INFO] signed certificate with serial number 322870458312346521644247569939536038641391898096
2021/11/14 00:47:42 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").

生成普通用户kubeconfig文件

root@k8s-ansible-client:~/yaml/20211031/role# kubectl config set-cluster k8s-pop --certificate-authority=/etc/kubernetes/ssl/ca.pem --embed-certs=true --server=https://192.168.20.201:6443 --kubeconfig=pop.kubeconfig
Cluster "k8s-pop" set.

root@k8s-ansible-client:~/yaml/20211031/role# cat pop.kubeconfig 
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR1RENDQXFDZ0F3SUJBZ0lVQmNTQWZraUVkdFpPM0pGYjZZNmpza1JEdEJnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1lURUxNQWtHQTFVRUJoTUNRMDR4RVRBUEJnTlZCQWdUQ0VoaGJtZGFhRzkxTVFzd0NRWURWUVFIRXdKWQpVekVNTUFvR0ExVUVDaE1EYXpoek1ROHdEUVlEVlFRTEV3WlRlWE4wWlcweEV6QVJCZ05WQkFNVENtdDFZbVZ5CmJtVjBaWE13SUJjTk1qRXdPVEkwTVRRME56QXdXaGdQTWpFeU1UQTRNekV4TkRRM01EQmFNR0V4Q3pBSkJnTlYKQkFZVEFrTk9NUkV3RHdZRFZRUUlFd2hJWVc1bldtaHZkVEVMTUFrR0ExVUVCeE1DV0ZNeEREQUtCZ05WQkFvVApBMnM0Y3pFUE1BMEdBMVVFQ3hNR1UzbHpkR1Z0TVJNd0VRWURWUVFERXdwcmRXSmxjbTVsZEdWek1JSUJJakFOCkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQTQyRkhYRlV5VHpGRGtLNGRZQmNVOVBDb2doUUQKMzM1WUU2N1UwK3poK0FVd201c0IvbGVBM25wMGVCakdqUzhnVTM3Y0t1V0haQ3BTMHpLZjF5dTR0VE9ia0p2UwovamxlZWI5ekxSQTEwc1NMVHRNV1cxMWxLNm84cWtmeWh5eFJxN0RqaGhGRTZxWndzWWxqR2t6Wm1zak5TelVBCmdhcy9YQkNOYVVBajZvWVBJc1lkMlJQeGFvVzA4VHd1UVRXcURCbkNUaWZ5c2xaUzdPUWNWL2FZQWVKQ3BPMHUKcnY3dlJRSCs0V3ZNTThyRHdyaSs1aHBsUi9ITnJHSWMzUVI5V3R3VFNhUm9RNzlHMHdGTXVvT0xKcXhNVnVoVQpKSnYxdzRYUlpkOVlYYlVkZVFMYVRpWTZMRE1ycXovYnpEdGs3YkVaRE5JN2ZqVTB6c3pkRGhEVjJ3SURBUUFCCm8yWXdaREFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBakFkQmdOVkhRNEUKRmdRVTZhUzJpcWlwVW16UHdIQ2h2ZkhtUjA1NG9ySXdId1lEVlIwakJCZ3dGb0FVNmFTMmlxaXBVbXpQd0hDaAp2ZkhtUjA1NG9ySXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQVlSb0V5YVZ2akRSRlZDMWFoTVFaMTJwTGhPCnBDQlJGN2pSREE4VUs4RHArUmh2bVRkNGlNb1QvejM4MnFEcjlnTnU0bGp6V0FRalI0UWcyZU5aVEkzYnUyN2YKcGUxZGZCSGpVUEtXTWRUeDd3d3ZyK041UElXZzBEVVpabmFqenZkOWVOVkZEN2Q4ejRIUEl6ZEVyTHQ5b1l0MQpMTDBhdVhxdkdRbU9SUExNVXZEMTY2RWVSQVZtYlo0WFhPSkI2LzZDWWdiK2xYNXQ2WWRaZm5PYUswaWRNQ1RyCkc4WDd6eTdOSUVOTW1tWUtHekRPTStRYUpoUHYwbFJoWUNRd0dQMElRVlBPSjErL3ZoVU9WQUVMdmZWTnV2VkkKQU4xM0c3Qm9WUTdoQ00yOE5GRWJ1RWFtYVNSc1pIa1o1U1d4YzZVdWFnNUx6QW5JZUpTWCs0bE9lUk09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    server: https://192.168.20.201:6443
  name: k8s-pop
contexts: null
current-context: ""
kind: Config
preferences: {}
users: null

设置客户端认证参数

root@k8s-ansible-client:~/yaml/20211031/role# kubectl config set-credentials pop \
> --client-certificate=pop.pem \
> --client-key=pop-key.pem \
> --embed-certs=true \
> --kubeconfig=pop.kubeconfig
User "pop" set.
root@k8s-ansible-client:~/yaml/20211031/role# cat pop.kubeconfig 
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR1RENDQXFDZ0F3SUJBZ0lVQmNTQWZraUVkdFpPM0pGYjZZNmpza1JEdEJnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1lURUxNQWtHQTFVRUJoTUNRMDR4RVRBUEJnTlZCQWdUQ0VoaGJtZGFhRzkxTVFzd0NRWURWUVFIRXdKWQpVekVNTUFvR0ExVUVDaE1EYXpoek1ROHdEUVlEVlFRTEV3WlRlWE4wWlcweEV6QVJCZ05WQkFNVENtdDFZbVZ5CmJtVjBaWE13SUJjTk1qRXdPVEkwTVRRME56QXdXaGdQTWpFeU1UQTRNekV4TkRRM01EQmFNR0V4Q3pBSkJnTlYKQkFZVEFrTk9NUkV3RHdZRFZRUUlFd2hJWVc1bldtaHZkVEVMTUFrR0ExVUVCeE1DV0ZNeEREQUtCZ05WQkFvVApBMnM0Y3pFUE1BMEdBMVVFQ3hNR1UzbHpkR1Z0TVJNd0VRWURWUVFERXdwcmRXSmxjbTVsZEdWek1JSUJJakFOCkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQTQyRkhYRlV5VHpGRGtLNGRZQmNVOVBDb2doUUQKMzM1WUU2N1UwK3poK0FVd201c0IvbGVBM25wMGVCakdqUzhnVTM3Y0t1V0haQ3BTMHpLZjF5dTR0VE9ia0p2UwovamxlZWI5ekxSQTEwc1NMVHRNV1cxMWxLNm84cWtmeWh5eFJxN0RqaGhGRTZxWndzWWxqR2t6Wm1zak5TelVBCmdhcy9YQkNOYVVBajZvWVBJc1lkMlJQeGFvVzA4VHd1UVRXcURCbkNUaWZ5c2xaUzdPUWNWL2FZQWVKQ3BPMHUKcnY3dlJRSCs0V3ZNTThyRHdyaSs1aHBsUi9ITnJHSWMzUVI5V3R3VFNhUm9RNzlHMHdGTXVvT0xKcXhNVnVoVQpKSnYxdzRYUlpkOVlYYlVkZVFMYVRpWTZMRE1ycXovYnpEdGs3YkVaRE5JN2ZqVTB6c3pkRGhEVjJ3SURBUUFCCm8yWXdaREFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBakFkQmdOVkhRNEUKRmdRVTZhUzJpcWlwVW16UHdIQ2h2ZkhtUjA1NG9ySXdId1lEVlIwakJCZ3dGb0FVNmFTMmlxaXBVbXpQd0hDaAp2ZkhtUjA1NG9ySXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQVlSb0V5YVZ2akRSRlZDMWFoTVFaMTJwTGhPCnBDQlJGN2pSREE4VUs4RHArUmh2bVRkNGlNb1QvejM4MnFEcjlnTnU0bGp6V0FRalI0UWcyZU5aVEkzYnUyN2YKcGUxZGZCSGpVUEtXTWRUeDd3d3ZyK041UElXZzBEVVpabmFqenZkOWVOVkZEN2Q4ejRIUEl6ZEVyTHQ5b1l0MQpMTDBhdVhxdkdRbU9SUExNVXZEMTY2RWVSQVZtYlo0WFhPSkI2LzZDWWdiK2xYNXQ2WWRaZm5PYUswaWRNQ1RyCkc4WDd6eTdOSUVOTW1tWUtHekRPTStRYUpoUHYwbFJoWUNRd0dQMElRVlBPSjErL3ZoVU9WQUVMdmZWTnV2VkkKQU4xM0c3Qm9WUTdoQ00yOE5GRWJ1RWFtYVNSc1pIa1o1U1d4YzZVdWFnNUx6QW5JZUpTWCs0bE9lUk09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    server: https://192.168.20.201:6443
  name: k8s-pop
contexts: null
current-context: ""
kind: Config
preferences: {}
users:
- name: pop
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJpZ0F3SUJBZ0lVT0k0REYzdmg0NHBNTndtTmVjb0tUdXA0RGZBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1lURUxNQWtHQTFVRUJoTUNRMDR4RVRBUEJnTlZCQWdUQ0VoaGJtZGFhRzkxTVFzd0NRWURWUVFIRXdKWQpVekVNTUFvR0ExVUVDaE1EYXpoek1ROHdEUVlEVlFRTEV3WlRlWE4wWlcweEV6QVJCZ05WQkFNVENtdDFZbVZ5CmJtVjBaWE13SUJjTk1qRXhNVEV6TVRZME16QXdXaGdQTWpBM01URXhNREV4TmpRek1EQmFNR0F4Q3pBSkJnTlYKQkFZVEFrTk9NUkF3RGdZRFZRUUlFd2RDWldsS2FXNW5NUkF3RGdZRFZRUUhFd2RDWldsS2FXNW5NUXd3Q2dZRApWUVFLRXdOck9ITXhEekFOQmdOVkJBc1RCbE41YzNSbGJURU9NQXdHQTFVRUF4TUZRMmhwYm1Fd2dnRWlNQTBHCkNTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFER2lJS2VHRTJMQ3VCZGFqUjF5ZVMrYUZTQThTODgKNUdNUXNEQ3NLOEdQYjVuRTVXNUhFQ3hDYlBJeisrenc0Y3lMelhyakxOM3hURzUwWUlPc1kwQmVSYXpJNnZWbwpKWEUvd0pSeUJxTmxweDkxcVYwK1VkR1M2S2ZCdkVFNlhDdzc5emE4d1F3QkEzN016OW5McGtHWkpqWVoyOThzCkZDR3kwb3FZSk1HTFZpNzVZb3VxS2QreHY1R21Jck5kZnl0K3h2OUlHV2hRN3dhWWx2V0VDd0dRaHU0RkhsbW0KRGQyZmNzRlFCcHRBT1lVVmNmZzZYRmVlSVd5dlNEeE5wTDdoNFNQVllyNFgrS05zZ202ZExFaW15eWRJZmNuWAo3SndlcFRPOXRJMnFiV3dvQ2VyRzVIbnBuWDErdUdwY0s3cElBZE5sRktVNFp2QTljZ0VyZVJJUkFnTUJBQUdqCmZ6QjlNQTRHQTFVZER3RUIvd1FFQXdJRm9EQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFkQmdOVkhRNEVGZ1FVNkszaVNQRWFuM1lSeHcveEt5NndwNE8zdmo0dwpId1lEVlIwakJCZ3dGb0FVNmFTMmlxaXBVbXpQd0hDaHZmSG1SMDU0b3JJd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBSzd5cDVZd09nVFF6TXBEeElHM3hLOVV1dnVHbS8zNXNTWHNQVWpnVHRaWUJuNnVUdGFkaUw0NlBIcHIKVVhDUGhxUEYvOHlYZE1UKzZJTysxcytndmNOSjkwZnFJTXdyTFhoc1ZzaVNSZUpwS3FoN3c0ZnFrY1EyV05PRwoxNFRlN3hTbnUraWFRZnM5UFpuRndvRzZ3QWtqRWdiNG9IdEk3R1JST3JKKzBZVU9oTHVuRHN0NEN6NFZvSUdmCndiRFgxSlNwUTVKbnlZWndRN1NkVE1NNUxoSC9QNkc3TjdKOWxFR3RUQVdvb2RmQTZNSFloYmxMRTcrQWlYYS8KeTVaTDMvelE5MXVtcEJGcnkvUi9kaWxvaHlrTW9aVmRDZ3BxcDBFanhQTklsRXdNYVJIRk85NmhWdnV3K3YxUApMbDAxMDJsUnI0RTNZV3pLVzU4NEc5VzN3UkE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBeG9pQ25oaE5pd3JnWFdvMGRjbmt2bWhVZ1BFdlBPUmpFTEF3ckN2QmoyK1p4T1Z1ClJ4QXNRbXp5TS92czhPSE1pODE2NHl6ZDhVeHVkR0NEckdOQVhrV3N5T3IxYUNWeFA4Q1VjZ2FqWmFjZmRhbGQKUGxIUmt1aW53YnhCT2x3c08vYzJ2TUVNQVFOK3pNL1p5NlpCbVNZMkdkdmZMQlFoc3RLS21DVEJpMVl1K1dLTApxaW5mc2IrUnBpS3pYWDhyZnNiL1NCbG9VTzhHbUpiMWhBc0JrSWJ1QlI1WnBnM2RuM0xCVUFhYlFEbUZGWEg0Ck9seFhuaUZzcjBnOFRhUys0ZUVqMVdLK0YvaWpiSUp1blN4SXBzc25TSDNKMSt5Y0hxVXp2YlNOcW0xc0tBbnEKeHVSNTZaMTlmcmhxWEN1NlNBSFRaUlNsT0did1BYSUJLM2tTRVFJREFRQUJBb0lCQUdmQVpVcExkeEtudjNMeQpFckpQclF2WXAvaXVram9uUEtJM0FXaW9nVUg5VjRXdlJMODhjM1RQVEkvZ0l3WUxhb0xSQWx5QVVRaE9JaGNOCmJTS0V4OW04WGJ5dUZVdTA3WWNjbERjMnd1Tlh3RGdVSjFkdkdLL0dpQXpWM2R5cTJLOEoxWUEwL3BuMUFxbjAKSVdTczRQRXhKK3JCbmRLQ1BzNGQrekhoVzRmOXVXUnBrdkVIWS82c0ZuZ3dYVUI1bTA1SkRGZUJwWkZtSDEvRgpJRmZyY3FmNTR1YldkRGk3YWlxTG54ZHdNUG9WRnNKdnhneDNQcTBjdjBEUHZDOFVNN2h5MHpnb1h1SERCSW92CkdDeWw3c3I3NmVKcHgweE1yVFQ0RGdlWENzUzIvcXRHNDYxaVJ5a0lYVHhkc2M1RWNrcmZ4QVlUdVZ3QXF5eDcKNU4xTjh2RUNnWUVBNW0rMENKYXJFSlo5Sm8rL3hzZ0VPY2JBd2FnSHY1VnFCVlE1ZmhFbTdxcUZrNC96aUhsYwpSdXA2OUp1c3FnWm5Cd0dtMGVOOHdYT3AwcHYvcVlMUDBtNXpISjZTM1ozQ1Y3ZFFmenZ1dlE1MEpxM1BYY0RHCkxhbUtFazBsMzBROGhoQjlJRTB4R1ZTMUl2UHovRHFaeHA2TFdMUDZhaDZNL3g5Y3JXWnpkTDBDZ1lFQTNJN0YKcmt3MEJHdy9OQUVvMjR6aXEvNzAwOWpZdnhUWUhDdFFVVnVHMHJBWUNqWXd4RGczQXlnK2NnbEtsL3ZWbEtsdQp0dUFSbXlRdmNFdVRTR1FOV0Ewb1F4Z0UrZ1J4SWxPV0NIOGxVUXZ1Szh4RGFzQjdRK1JQNnJDNy9YSVZ3V0hCCkcwSjBxdzZWZVBkSzVmTk16cEg4QTlpQTJ0WTBraWg4ejdwakNlVUNnWUFyNVROaVAzRXVvN3dMVUc2enF2NUQKRXowOHBvbHpVVDcwN09wV3ZXV3hLUUp3N1lieWhFdXpwbzd0Y1lvZWlVR3U3LzJiRmI1NkMxSmFNQ1V2WVIrOQpjaFN6YXZHSERib3JnMXZ1SUxpRmd1OVZQdDYxZVRkSEUzaWRxOXgrL3p5WVBTUFl0MXVXKzYvVmpLcjViU0JGCjJZV3B5Lzd6b0FZend3R2dkbGVmOFFLQmdCdnNNMWlxcXhjNFFSUXJaV25PUDFBNUdmUE1Denk5dmRKckpXTDMKYkcwMkFBVWk4UytXVWxpaStxempRajlWa2FlZGY3ZkZURlZRMG5Tc0RMeG9ka3dFZG1sd0hBa3ZFTWVndjJqWgo5L1ozeFRKa1ROQ3lCNmtEdVo1anU1a05uWFY3RThDSXZFNS9yU3JBWEFvYXNFbWlyNzRvNWI5T3lSOEw0eWxGClZvNkZBb0dCQU1naFU1LzBHOFM5eEk4a05kSFltZkxqS3VtZUR6ZmxSNGwyUDhoZE96UElwbmc5NlN2dGhtaW8KQnduVjk5U2hzMkhGMFNsQzZ3VCtJbCtrc2Vta0VyeHduQWh4ZThJb1NYcklMVWp4NFlNdXhLT2pLaXh5emJrdwpkMG42ZUJFMXhCcTk2ZmlFZFNRcVIrbHY1R0djYVNHb3UySlRTbDc0cW03ZU1YY2x2RjdLCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==

设置上下文参数(多集群使用上下文区分)

root@k8s-ansible-client:~/yaml/20211031/role# kubectl config set-context k8s-pop \
> --cluster=k8s-pop \
> --user=pop \
> --namespace=pop \
> --kubeconfig=pop.kubeconfig
Context "k8s-pop" created.

设置默认上下文

root@k8s-ansible-client:~/yaml/20211031/role# kubectl config use-context k8s-pop --kubeconfig=pop.kubeconfig
Switched to context "k8s-pop".

将pop用户token写入用户kube-config文件

root@k8s-ansible-client:~/yaml/20211031/role# kubectl describe secrets pop-token-spgzj -n pop
Name:         pop-token-spgzj
Namespace:    pop
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: pop
              kubernetes.io/service-account.uid: 4125ee58-e8cd-4d6a-b01d-9af9049edcb8

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1350 bytes
namespace:  3 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IlA3YjBkSVE3QWlJdzRNOVlfcGpHWWI3dTU3OUhtczZTVGJldk91TS1pejQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJwb3AiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoicG9wLXRva2VuLXNwZ3pqIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6InBvcCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQxMjVlZTU4LWU4Y2QtNGQ2YS1iMDFkLTlhZjkwNDllZGNiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpwb3A6cG9wIn0.MAZrhLoxXzm5CcGaRoSnJmXXVCKTins1wijOFE0OMLKSJO5sUQigd0w55veZS--1rrbLqOBENhQ7Mr5ULPBpH9ARR7TCSby1syG-fFRoWdRXuGCgA6Sy79LsWIFtiE0e93bFS7LfpfsdBdbDzDlccKxLmYfiCLjGqc60CJNWSP9W5woYIZpcOPGNnTa14cBQ44tN5hMQS5o0qGDfM05uXx_Q_mnyk8kbieJnrmNVFn5-9EiX8ELryaJ_onDKxLuILH8aoE2IE0oTuDuqAC178wilFbV2-2v38x4LEYW0s3AFO9T9x62kSdBh2I94dUb5Ai3AthwlEK9J7sVHrTJ0sA

# token放在最后
root@k8s-ansible-client:~/yaml/20211031/role# vim pop.kubeconfig
...
    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IlA3YjBkSVE3QWlJdzRNOVlfcGpHWWI3dTU3OUhtczZTVGJldk91TS1pejQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJwb3AiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoicG9wLXRva2VuLXNwZ3pqIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6InBvcCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQxMjVlZTU4LWU4Y2QtNGQ2YS1iMDFkLTlhZjkwNDllZGNiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpwb3A6cG9wIn0.MAZrhLoxXzm5CcGaRoSnJmXXVCKTins1wijOFE0OMLKSJO5sUQigd0w55veZS--1rrbLqOBENhQ7Mr5ULPBpH9ARR7TCSby1syG-fFRoWdRXuGCgA6Sy79LsWIFtiE0e93bFS7LfpfsdBdbDzDlccKxLmYfiCLjGqc60CJNWSP9W5woYIZpcOPGNnTa14cBQ44tN5hMQS5o0qGDfM05uXx_Q_mnyk8kbieJnrmNVFn5-9EiX8ELryaJ_onDKxLuILH8aoE2IE0oTuDuqAC178wilFbV2-2v38x4LEYW0s3AFO9T9x62kSdBh2I94dUb5Ai3AthwlEK9J7sVHrTJ0sA

测试登录

image.png

image.png

3. k8s 网络

容器网络主要问题:

  • 高度耦合的容器间通信
  • Pod与Pod之间通信
  • Pod与Service之间通信
  • Pod到外网之间通信

kubernetes CNI
CNI是Container Network Interface的缩写,它是一个通用的容器网络插件的k8s网络接口,开源社区里已经有了很多实现容器网络的方案,不同的网络实现方案在k8s内都是以插件调用的形式工作,所以这里需要一个统一的标准接口。如果将k8s的Pod视为一台“虚拟机”,那么网络插件的工作就是管理这台虚拟机的网络栈,包括给这台虚拟机插入网卡、配置路由、IP等;而CNI的工作则是对接网络插件和kubelet容器运行时管理工具(对于docker容器运行时来说实际上是dockershim),主要体现在Pod的创建和删除过程

CNI插件常用的三种实现模式:
Overlay: 靠隧道打通,不依赖底层网络
macvlan:靠路由打通,部分依赖底层网络
underlay :靠底层网络能力打通,强依赖底层

3.1 Overlay

Overlay 在网络技术领域,指的是一种网络架构上叠加的虚拟化技术模式,其大体框架是对基础网络不进行大规模修改的条件下,实现应用在网络上的承载,并能与其它网络业务分离,并且以基于IP的基础网络技术为主。Overlay 技术是在现有的物理网络之上构建一个虚拟网络,上层应用只与虚拟网络相关。一个Overlay网络主要由三部分组成:

  • 边缘设备:是指与虚拟机直接相连的设备
  • 控制平面:主要负责虚拟隧道的建立维护以及主机可达性信息的通告
  • 转发平面:承载 Overlay 报文的物理网络。

3.1.1 网络实现方式

VLAN
VLAN (Virtual Local Area Network)意为虚拟局域网,是在交换机实现过程中涉及到的概念,由802.1Q标准所定义。由于交换机是工作在链路层的网络设备,连接在同一台交换机的终端处于同一个三层网中,同时也处于同一个广播域。当交换机接入较多的终端时,任意一台终端发送广播报文时(例如:ARP请求),报文都会传遍整个网络。对于规模较大的组网场景,广播报文的泛滥对于网络通信将会造成较大的影响。VLAN技术为这一问题提供了解决方案,VLAN将同一网络划分为多个逻辑上的虚拟子网,并规定当收到广播报文时,仅仅在其所在VLAN中进行广播从而防止广播报文泛滥。VLAN技术在链路层的层次中实现了广播域的隔离。

image.png

VID字段字段唯一标识了一个VLAN,12bit长度的VID可以表示4096个不同的值,除去两个保留值,一个以太网最多可以划分为4094个VLAN。

VXLAN
VXLAN由RFC7348定义,这是2014年定稿的一个协议,VXLAN协议将Ethernet帧封装在UDP内,再加上8个字节的VXLAN header,用来标识不同的二层网络。VXLAN在三层网络基础上构建大二层网络,其物理承载是传统三层网络以及专用的VTEP设备。VTEP(VXLAN Tunnel EndPoint)负责VXLAN报文的封装和解封,对虚拟机隐藏了其链路层帧的转发细节。

image.png

  • 最内层的是VXLAN报文,由VXLAN报文头部和L2帧组成。其中头部包含最重要的字段是VNID,类似于VLAN中的VID,用于分隔虚拟网络的二层域空间;
  • VXLAN报文被VTEP封装在UDP报文中,由VTEP负责封装和解封;
  • Mac帧头部和IP头部也是由VTEP进行装载,传输的过程就是从本地虚拟机所属的VTEP到目的虚拟机所属的VTEP,对虚拟机隐藏底层传输细节。

VXLAN与VLAN的最大区别在于,VLAN只是修改了原始的以太网帧头部,但是整个网络数据包还是原来那个数据包,而VXLAN是将原始的以太网帧隐藏在UDP数据里面。经过VTEP封装之后,在网络线路上看起来只有VTEP之间的UDP数据传递,传输细节对虚拟机透明,从而做到了在L3网络上构建虚拟L2网络。

VXLAN网络中传输的建立依赖于VTEP的组播实现虚拟L2网络的ARP功能。假设虚拟机A与虚拟机B要进行通信,虚拟机A会将以太网帧发送给本地VTEP A,VTEP A将该数据帧封装后进行端到端传输,发送给虚拟机B的本地VTEP B,接下来就是解封数据帧再广播给虚拟机B的过程了。

优势:
相比VLAN所支持的二层网络数量多更多
更为灵活的虚拟机部署——VXLAN可以实现构建在三层网络上的大二层网络网络,通过UDP在L3网络上传输L2网络数据,便于虚拟机迁移等。

缺点:
VXLAN是一种overlay网络,不能独立存在,必须依赖underlay网络,而在构建underlay网络时,还是需要借助VLAN。
VXLAN的优势是在大规模环境下,如果数据中心的规模较小,那么VXLAN将会浪费很多网络性能。
需要专属设备支持。
VXLAN因为需要进行外层封装,每个以太网帧的传输都会浪费50字节,对于小报文的传输将会有极大的浪费。

3.1.2 Flannel

简介

Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。

在默认的Docker配置中,每个节点上的Docker服务会分别负责所在节点容器的IP分配。这样导致的一个问题是,不同节点上容器可能获得相同的内外IP地址。并使这些容器之间能够之间通过IP地址相互找到,也就是相互ping通。

Flannel的设计目的就是为集群中的所有节点重新规划IP地址的使用规则,从而使得不同节点上的容器能够获得“同属一个内网”且”不重复的”IP地址,并让属于不同节点上的容器能够直接通过内网IP通信。

Flannel实质上是一种“覆盖网络(overlaynetwork)”,也就是将TCP数据包装在另一种网络包里面进行路由转发和通信,目前已经支持udp、vxlan、host-gw、aws-vpc、gce和alloc路由等数据转发方式,默认的节点间数据通信方式是UDP转发。

特点

  • 使集群中的不同Node主机创建的Docker容器都具有全集群唯一的虚拟IP地址。

  • 建立一个覆盖网络(overlay network),通过这个覆盖网络,将数据包原封不动的传递到目标容器。覆盖网络是建立在另一个网络之上并由其基础设施支持的虚拟网络。覆盖网络通过将一个分组封装在另一个分组内来将网络服务与底层基础设施分离。在将封装的数据包转发到端点后,将其解封装。

  • 创建一个新的虚拟网卡flannel0接收docker网桥的数据,通过维护路由表,对接收到的数据进行封包和转发(vxlan)。

  • etcd保证了所有node上flanned所看到的配置是一致的。同时每个node上的flanned监听etcd上的数据变化,实时感知集群中node的变化。

架构图

image.png

Cni0:网桥设备,每创建一个pod都会创建一对 veth pair。其中一端是pod中的eth0,另一端是Cni0网桥中的端口(网卡)。Pod中从网卡eth0发出的流量都会发送到Cni0网桥设备的端口(网卡)上。

Flannel.1: overlay网络的设备,用来进行 vxlan 报文的处理(封包和解包)。不同node之间的pod数据流量都从overlay设备以隧道的形式发送到对端。

Flanneld:flannel在每个主机中运行flanneld作为agent,它会为所在主机从集群的网络地址空间中,获取一个小的网段subnet,本主机内所有容器的IP地址都将从中分配。同时Flanneld监听K8s集群数据库,为flannel.1设备提供封装数据时必要的mac,ip等网络数据信息。

不同node上的pod的通信流程:

  • pod中产生数据,根据pod的路由信息,将数据发送到Cni0
  • Cni0 根据节点的路由表,将数据发送到隧道设备flannel.1
  • Flannel.1查看数据包的目的ip,从flanneld获得对端隧道设备的必要信息,封装数据包。
  • Flannel.1将数据包发送到对端设备。对端节点的网卡接收到数据包,发现数据包为overlay数据包,解开外层封装,并发送内层封装到flannel.1设备。
  • Flannel.1设备查看数据包,根据路由表匹配,将数据发送给Cni0设备。
  • Cni0匹配路由表,发送数据给网桥上对应的端口。

存在的问题

  • 不支持pod之间的网络隔离。Flannel设计思想是将所有的pod都放在一个大的二层网络中,所以pod之间没有隔离策略。

  • 设备复杂,效率不高。Flannel模型下有三种设备,数量经过多种设备的封装、解析,势必会造成传输效率的下降。

3.1.3 Calico

Calico 是一种容器之间互通的网络方案。在虚拟化平台中,比如 OpenStack、Docker 等都需要实现 workloads 之间互连,但同时也需要对容器做隔离控制,就像在 Internet 中的服务仅开放80端口、公有云的多租户一样,提供隔离和管控机制。而在多数的虚拟化平台实现中,通常都使用二层隔离技术来实现容器的网络,这些二层的技术有一些弊端,比如需要依赖 VLAN、bridge 和隧道等技术,其中 bridge 带来了复杂性,vlan 隔离和 tunnel 隧道则消耗更多的资源并对物理环境有要求,随着网络规模的增大,整体会变得越加复杂。我们尝试把 Host 当作 Internet 中的路由器,同样使用 BGP 同步路由,并使用 iptables 来做安全访问策略,最终设计出了 Calico 方案。

特点
1.更优的资源利用

二层网络通讯需要依赖广播消息机制,广播消息的开销与 host 的数量呈指数级增长,Calico 使用的三层路由方法,则完全抑制了二层广播,减少了资源开销。

另外,二层网络使用 VLAN 隔离技术,天生有 4096 个规格限制,即便可以使用 vxlan 解决,但 vxlan 又带来了隧道开销的新问题。而 Calico 不使用 vlan 或 vxlan 技术,使资源利用率更高。

2.可扩展性

Calico 使用与 Internet 类似的方案,Internet 的网络比任何数据中心都大,Calico 同样天然具有可扩展性。

3.简单而更容易 debug

因为没有隧道,意味着 workloads 之间路径更短更简单,配置更少,在 host 上更容易进行 debug 调试。

4.更少的依赖

Calico 仅依赖三层路由可达。

5.可适配性

Calico 较少的依赖性使它能适配所有 VM、Container、白盒或者混合环境场景。

架构图

image.png

Calico网络模型主要工作组件:

  • Felix:运行在每一台 Host 的 agent 进程,主要负责网络接口管理和监听、路由、ARP 管理、ACL 管理和同步、状态上报等。

  • etcd:分布式键值存储,主要负责网络元数据一致性,确保Calico网络状态的准确性,可以与kubernetes共用;

  • BGP Client(BIRD):Calico 为每一台 Host 部署一个 BGP Client,使用 BIRD 实现,BIRD 是一个单独的持续发展的项目,实现了众多动态路由协议比如 BGP、OSPF、RIP 等。在 Calico 的角色是监听 Host 上由 Felix 注入的路由信息,然后通过 BGP 协议广播告诉剩余 Host 节点,从而实现网络互通。

  • BGP Route Reflector:在大型网络规模中,如果仅仅使用 BGP client 形成 mesh 全网互联的方案就会导致规模限制,因为所有节点之间俩俩互联,需要 N^2 个连接,为了解决这个规模问题,可以采用 BGP 的 Router Reflector 的方法,使所有 BGP Client 仅与特定 RR 节点互联并做路由同步,从而大大减少连接数。

Felix

Felix会监听ECTD中心的存储,从它获取事件,比如说用户在这台机器上加了一个IP,或者是创建了一个容器等。用户创建pod后,Felix负责将其网卡、IP、MAC都设置好,然后在内核的路由表里面写一条,注明这个IP应该到这张网卡。同样如果用户制定了隔离策略,Felix同样会将该策略创建到ACL中,以实现隔离。

BIRD

BIRD是一个标准的路由程序,它会从内核里面获取哪一些IP的路由发生了变化,然后通过标准BGP的路由协议扩散到整个其他的宿主机上,让外界都知道这个IP在这里,你们路由的时候得到这里来。

架构特点

由于Calico是一种纯三层的实现,因此可以避免与二层方案相关的数据包封装的操作,中间没有任何的NAT,没有任何的overlay,所以它的转发效率可能是所有方案中最高的,因为它的包直接走原生TCP/IP的协议栈,它的隔离也因为这个栈而变得好做。因为TCP/IP的协议栈提供了一整套的防火墙的规则,所以它可以通过IPTABLES的规则达到比较复杂的隔离逻辑。

Calico 网络模式
IPIP
从字面来理解,就是把一个IP数据包又套在一个IP包里,即把 IP 层封装到 IP 层的一个 tunnel。它的作用其实基本上就相当于一个基于IP层的网桥!一般来说,普通的网桥是基于mac层的,根本不需 IP,而这个 ipip 则是通过两端的路由做一个 tunnel,把两个本来不通的网络通过点对点连接起来。

image.png

BGP
边界网关协议(Border Gateway Protocol, BGP)是互联网上一个核心的去中心化自治路由协议。它通过维护IP路由表或‘前缀’表来实现自治系统(AS)之间的可达性,属于矢量路由协议。BGP不使用传统的内部网关协议(IGP)的指标,而使用基于路径、网络策略或规则集来决定路由。因此,它更适合被称为矢量性协议,而不是路由协议。BGP,通俗的讲就是讲接入到机房的多条线路(如电信、联通、移动等)融合为一体,实现多线单IP,BGP 机房的优点:服务器只需要设置一个IP地址,最佳访问路由是由网络上的骨干路由器根据路由跳数与其它技术指标来确定的,不会占用服务器的任何系统。


image.png

两种网络对比
IPIP网络:
流量:tunl0设备封装数据,形成隧道,承载流量
适用网络类型:适用于互相访问的Pod不在同一个网段中,跨网段访问的场景,外层封装的IP能够解决跨网段的路由问题。
效率:流量需要tunl0设备封装,效率略低
BGP网络:
流量:使用路由信息导向流量
使用网络类型:适用于互相访问的Pod在同一个网段。
效率:原生hostGW,效率高

存在的问题
1.缺点租户隔离问题
Calico 的三层方案是直接在 host 上进行路由寻址,那么对于多租户如果使用同一个 CIDR 网络就面临着地址冲突的问题。
2.路由规模问题
通过路由规则可以看出,路由规模和 pod 分布有关,如果 pod离散分布在 host 集群中,势必会产生较多的路由项。
3.iptables规则规模问题
1台Host上可能虚拟化十几或几十个容器实例,过多的 iptables 规则造成复杂性和不可调试性,同时也存在性能损耗。
4.跨子网时的网关路由问题
当对端网络不为二层可达时,需要通过三层路由机时,需要网关支持自定义路由配置,即 pod 的目的地址为本网段的网关地址,再由网关进行跨三层转发。

3.2 underlay

Underlay网络模型Underlay网络就是传统IT基础设施网络,由交换机和路由器等设备组成,借助以太网协议、路由协议和VLAN协议等驱动,它还是Overlay网络的底层网络,为Overlay网络提供数据通信服务。容器网络中的Underlay网络是指借助驱动程序将宿主机的底层网络接口直接暴露给容器使用的一种网络构建技术,较为常见的解决方案有MAC VLAN、IP VLAN和直接路由等。

1.MAC VLANMAC VLAN支持在同一个以太网接口上虚拟出多个网络接口,每个虚拟接口都拥有唯一的MAC地址,并可按需配置IP地址。通常这类虚拟接口被网络工程师称作子接口,但在MAC VLAN中更常用上层或下层接口来表述。与Bridge模式相比,MAC VLAN不再依赖虚拟网桥、NAT和端口映射,它允许容器以虚拟接口方式直接连接物理接口。图10-9给出了Bridge与MAC VLAN网络对比示意图。


image.png

VLAN网络对比MAC VLAN有Private、VEPA、Bridge和Passthru几种工作模式,它们各自的工作特性如下。

  • Private:禁止构建在同一物理接口上的多个MAC VLAN实例(容器接口)彼此间的通信,即便外部的物理交换机支持“发夹模式”也不行。
  • VPEA:允许构建在同一物理接口上的多个MAC VLAN实例(容器接口)彼此间的通信,但需要外部交换机启用发夹模式,或者存在报文转发功能的路由器设备。
  • Bridge:将物理接口配置为网桥,从而允许同一物理接口上的多个MAC VLAN实例基于此网桥直接通信,而无须依赖外部的物理交换机来交换报文;此为最常用的模式,甚至还是Docker容器唯一支持的模式。
  • Passthru:允许其中一个MAC VLAN实例直接连接物理接口。

由上述工作模式可知,除了Passthru模式外的容器流量将被MAC VLAN过滤而无法与底层主机通信,从而将主机与其运行的容器完全隔离,其隔离级别甚至高于网桥式网络模型,这对于有多租户需求的场景尤为有用。由于各实例都有专用的MAC地址,因此MAC VLAN允许传输广播和多播流量,但它要求物理接口工作于混杂模式,考虑到很多公有云环境中并不允许使用混杂模式,这意味着MAC VLAN更适用于本地网络环境。需要注意的是,MAC VLAN为每个容器使用一个唯一的MAC地址,这可能会导致具有安全策略以防止MAC欺骗的交换机出现问题,因为这类交换机的每个接口只允许连接一个MAC地址。另外,有些物理网卡存在可支撑的MAC地址数量上限。2. IP VLANIP VLAN类似于MAC VLAN,它同样创建新的虚拟网络接口并为每个接口分配唯一的IP地址,不同之处在于,每个虚拟接口将共享使用物理接口的MAC地址,从而不再违反防止MAC欺骗的交换机的安全策略,且不要求在物理接口上启用混杂模式,如图10-10所示。


image.png

IP VLAN有L2和L3两种模型,其中IP VLAN L2的工作模式类似于MAC VLAN Bridge模式,上层接口(物理接口)被用作网桥或交换机,负责为下层接口交换报文;

而IP VLAN L3模式中,上层接口扮演路由器的角色,负责为各下层接口路由报文,如图10-11所示。IP VLAN L2模型与MAC VLAN Bridge模型都支持ARP协议和广播流量,它们拥有直接接入网桥设备的网络接口,能够通过802.1d数据包进行泛洪和MAC地址学习。但IP VLAN L3模式下,网络栈在容器内处理,不支持多播或广播流量,从这个意义上讲,它的运行模式与路由器的报文处理机制相同。虽然支持多种网络模型,但MAC VLAN和IP VLAN不能同时在同一物理接口上使用。Linux内核文档中强调,MAC VLAN和IP VLAN具有较高的相似度,因此,通常仅在必须使用IP VLAN的场景中才不使用MAC VLAN。一般说来,强依赖于IP VLAN的场景有如下几个:

  • Linux主机连接到的外部交换机或路由器启用了防止MAC地址欺骗的安全策略;
  • 虚拟接口的需求数量超出物理接口能够支撑的容量上限,并且将接口置于混杂模式会给性能带来较大的负面影响;
  • 将虚拟接口放入不受信任的网络名称空间中可能会导致恶意的滥用。


    image.png

    需要注意的是,Linux内核自4.2版本后才支持IP VLAN网络驱动,且在Linux主机上使用ip link命令创建的802.1q配置接口不具有持久性,因此需依赖管理员通过网络启动脚本保持配置。

  1. 直接路由“直接路由”模型放弃了跨主机容器在L2的连通性,而专注于通过路由协议提供容器在L3的通信方案。这种解决方案因为更易于集成到现在的数据中心的基础设施之上,便捷地连接容器和主机,并在报文过滤和隔离方面有着更好的扩展能力及更精细的控制模型,因而成为容器化网络较为流行的解决方案之一。一个常用的直接路由解决方案如图10-12所示,每个主机上的各容器在二层通过网桥连通,网关指向当前主机上的网桥接口地址。跨主机的容器间通信,需要依据主机上的路由表指示完成报文路由,因此每个主机的物理接口地址都有可能成为另一个主机路由报文中的“下一跳”,这就要求各主机的物理接口必须位于同一个L2网络中。于是,在较大规模的主机集群中,问题的关键便转向如何更好地为每个主机维护路由表信息。常见的解决方案有:
    1> Flannel host-gw使用存储总线etcd和工作在每个节点上的flanneld进程动态维护路由;
    2>Calico使用BGP(Border Gateway Protocol)协议在主机集群中自动分发和学习路由信息。与Flannel不同的是,Calico并不会为容器在主机上使用网桥,而是仅为每个容器生成一对veth设备,留在主机上的那一端会在主机上生成目标地址,作为当前容器的路由条目,如图10-13所示。
直接路由虚拟网络示意图
Calico的直接路由模型示意图

Calico的直接路由模型示意图显然,较Overlay来说,无论是MAC VLAN、IP VLAN还是直接路由机制的Underlay网络模型的实现,它们因无须额外的报文开销而通常有着更好的性能表现,但对底层网络有着更多的限制条件。

3.3 macvlan

macvlan是kernel新支持的特性之一,需要Linux kernel v3.9–3.19和4.0+的支持。这种模式所配置的container网络同主机网络在同一个LAN里面,可以具有和主机一样的网络能力。

使用macvlan可以在主机的一个网络接口上配置多个虚拟的网络接口,这些网络接口有自己独立的MAC地址和IP地址。macvlan 下的container网络和主机在同一个网段中,共享同一个广播域。基于macvlan的联网方式,可以直接访问主机所在的LAN,并且没有其它诸如bridge等方式带来的bridge处理和地址翻译的负担,是一种高效直接的互联技术。

工作模式
macvlan可以在主机的网卡上绑定多个二层mac地址,每个mac地址对应主机网卡(主接口)的一个子接口,每个container可以绑定一个子接口作为自己的网卡接口。

根据子接口通信方式的不同,macvlan存在四种工作模式:

  • private mode:主接口会过滤掉交换机返回来的来自其子接口的报文,不同子接口之间无法互相通信。
  • vepa(Virtual Ethernet Port Aggregator) mode: 发送出去的报文经过交换机,交换机再发送到对应的目标地址(即使目标地址就是主机上的其它macvlan子接口),也就是hairpin mode模式,这个模式需要主接口连接的交换机支持 VEPA/802.1Qbg 特性;这种方式允许一个主接口上的多个子接口借助外部交换机进行相互通信,而LAN里面的广播包也会被主接口forward到所有子接口。这个种方式的一个典型应用是如果在外部交换机上有一些策略,则可以使用VEPA模式让所有子接口交互的包都会经由外部交换机的处理,便于统一管理整个子网的所有物理和虚拟接口。
  • bridge mode:通过主机上的macvlan bridge将主接口的所有子接口连接在一起,不同子接口之间能够不借助外部交换机而进行直接通信,不需要将报文发送到主机之外;因为所有子接口的mac地址都是已知的,所以macvlan bridge不需要mac地址学习和STP的能力,所以是一个高效的bridge实现。
  • passthru mode:container可以直接使用主机的网络接口,并具有对接口进行参数调整的能力。

需要注意的是,如果使用macvlan模式,虽然主接口和子接口在同一LAN,但是在主机上通过主接口是没有办法直接和子接口通信的;需要额外建立一个子接口,把主接口的IP配置给这个子接口,这样才能借助原来主接口的IP和子接口进行通信。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容