家用主机跑云原生全家桶 KuberSphere 3.1

家用主机跑云原生全家桶 KuberSphere 3.1

1.1 项目概述

本篇文章记录了我个人使用家用主机,在内网环境下,尽可能接近生产环境的配置,完成k8s平台KubeSphere实验环境的搭建。

1.2 主机配置

实验采用单主机的配置,主机安装Proxmox操作系统作为虚拟化平台,主机配置如下:

硬件配置总容量
处理器(s)AMD Ryzen 7 3700X8核 16线程
内存海盗船32GB *4128 GB
主板华擎B450 钢铁大版
NVMEHP SSD EX920 M21TB
SATA SSD光威 弈Pro 1T *44 TB
SATA SSD(ZFS Cache)Intel SSD 1T *22 TB
网卡板载1G

其中,光威 弈Pro SSD 组成ZFS Pool,Intel SSD 组成ZFS Pool Cache 。原本机房有淘汰的万兆网卡使用,可惜主板插了显卡,第二个PCI-E插槽无论如何都识别不到,只好放弃。

1.3 虚拟机规划

根据自己的多次实验和经验总结,假如按照生产高可用的要求安装Kubernetes,虚拟机规划有如下几种,根据资源占用情况排序:

在这里插入图片描述

  1. All-in-One套餐:,仅采用一台主机实现,ETCD、Kube-Master节点和Worker节点均只有一个,且位于同一台主机上。
  2. 乞丐套餐:ETCD、Kube-Master位于单节点上,3个Worker节点位于3台不同的主机上。
  3. 入门套餐:ETCD、Kube-Master、Worker位于同一个节点上,共3台。
  4. 生产准备套餐:ETCD、Kube-Master位于同节点上、各3个节点,3个Worker节点位于3台不同的主机上。
  5. 超级豪华套餐:ETCD组件与Kube-master分离,分别部署于不同主机上。

ETCD对网络吞吐量和磁盘IO要求非常高,在未对ETCD调优的前提下,单机ETCD需部署在固态硬盘上;

同时ETCD组成集群时,不仅要求ETCD部署在固态硬盘上,还要求网络至少是万兆以上。否则容易出现集群脑裂的现象。

根据多次安装KuberSphere全家桶失败实验总结,影响KuberSphere的稳定性原因非常多,但最主要的还是K8s的基础组件ETCD。除了网络原因外(没有使用万兆网卡、与集群共用网络),使用虚拟机完成3台ETCD部署对虚拟化主机磁盘IO要求依然很高,即使将3台虚机放在单个NVME存储上,或者放在多个SSD组成的ZFSPool内,仍然有一定的几率容易集群脑裂,进而导致Kubernetes故障。

所以为了保障集群稳定性,退而求其次,采用乞丐套餐,使用4台虚拟机完成本次容器化平台搭建。

主机类型数量CPU内存磁盘操作系统
Kube-Master & ETCD12C1T8GbNVME 64GDebain 9
Kube-Worker32C2T20GBZFS-SSD 64GDebain 9

1.4 网络规划

家用的环境下,我只有一个网件小路由作为边界路由,但毕竟只是千兆家用路由,实现LB、BGP之类的功能是不可能的,只能依靠软件实现。由于是单台主机,所有的虚拟机都在同一个主机上,使用ipref测试虚拟机之间的网络能够达到10GB/s,万兆通讯可以满足。

网络组件如下:

Kubernetes集群 CNI:Cacilo

内网&Kubernetes集群 DNS:CoreDNS

LB:PorterLB

IP规划:

组件地址
Kube-Master&ETCD10.0.0.172
Kube-Worker-110.0.0.30
Kube-Worker-210.0.0.173
Kube-Worker-310.0.0.60
EIP10.0.0.150-10.0.0.170
  1. 由于家用路由不支持BGP协议,PorterLB只能采用Layer2的方式实现LB共享出来的EIP。

  2. 且由于在KubeSphere内,每个项目(Namespace)都有自己独立的应用路由(Ingress),建议EIP主要分配给应用路由,实现业务的外网访问功能。

  3. 为了最大程度复用集群内的组件,CoreDNS可作为内网的DNS使用

1.5 存储规划

受制于单主机,存储的性能是实现云原生最大的阻碍,在经过多次惨痛的教训和实验后总结如下:

  1. 不建议采用Ceph:

    单台主机以虚拟机的方式实现Ceph性能非常低,除了内存、网络占用高之外,磁盘IO也上不去,即使是采用万兆网络+磁盘直通+SSD+ZFSPool的方式也无法满足ETCD、Redis等组件的要求,毕竟SATA3的限制在600MB/S。而采用NVME 速度虽然最高能满足PCI-E 3的速度3500MB/s,但是家用机基本上只有2个M.2接口,做不了直通,只能采用3台虚拟机实现,容量少、性能大打折扣。

  2. 不建议采用NFS:

    NFS存在单点故障,需要采用DRBD或ZFS的方式实现冗余,在实践的过程中模拟一台k8s虚拟机Down了的工况,虚拟机重启后业务上无法写入,需要重启NFS服务。此外,诸如Prometheus、ElasticSearch等观测性组件官方也不建议部署在NFS上。

  3. 采用LongHorn方式:

    实测采用LongHorn方式性能比Ceph、NFS高很多。

  4. 采用OpenEBS的local-volume 方式:

    最接近本地存储性能的方式,且KubeSphere官方安装工具kubekey默认支持此模式。

2. KubeSphere搭建

KubeSphere的安装步骤官方已经写的非常清晰,网上也有很多类似的教程,这里不过多描述。

搭建步骤建议如下:

  1. 下载KubeKey
  2. 各个主机安装必要安装包
  3. 安装Kubernetes
  4. 安装第三方分布式存储或CSI插件(如LongHorn、Rook等)
  5. 最小化安装KubeSphere
  6. 启用可插拔组件

3. Kubernetes常用配套基础插件

3.1 负载均衡PorterLB

LoadBalance插件是我在安装完Kubernetes和存储插件后第一个需要安装的配套插件。

总的来说MetalLB和PorterLB都支持BGP模式及Layer2模式,两个组件都测试过区别不大,虽然MetalLB发起较早,但考虑平台统一性,最终还是选择了青云配套的PorterLB。

3.1.1 PorterLB安装与配置

PorterLB安装可以仅采用单条命令安装。(推荐)

kubectl apply -f https://raw.githubusercontent.com/kubesphere/porter/master/deploy/porter.yaml

也可以Helm的方式或KubeSphere内置应用方式安装

3.1.2 PorterLB配置(重要)

由于与网络限制,我的PorterLB仅能在Layer2方式下运行,需要对kube-proxy的配置进行更改

kubectl edit configmap kube-proxy -n kube-system

在配置中找到ipvs的选项strictARP,设置为True(重要,否则设置不生效)

ipvs:
  strictARP: true

当节点上有多网卡时需要给node打上指定网卡ip的标签

kubectl annotate nodes master1 layer2.porter.kubesphere.io/v1alpha1="网卡IP"

接下来就是根据网络规划配置EIP,这里由于需要指定网卡,虚拟化部署的好处就体现出来了。

apiVersion: network.kubesphere.io/v1alpha2
kind: Eip
metadata:
  name: porter-layer2-eip
spec:
  address: 10.0.0.150-10.0.0.170
  #若单张网卡,不需要选定网卡
  #interface: eth0
  protocol: layer2

3.1.3 PorterLB使用

若使用PorterLB除了需要将Service的Tpye改成LoadBalancer之外,还需要再annotations内添加两个参数:

kind: Service
apiVersion: v1
metadata:
  name: porter-layer2-svc
  annotations:
  # 必须添加
    lb.kubesphere.io/v1alpha1: porter
  # 必须添加,若采用BGP方式则填写BGP
    protocol.porter.kubesphere.io/v1alpha1: layer2
  # 必须添加,名称与EIP配置相同,告知PorterLB使用哪个EIP配置
    eip.porter.kubesphere.io/v1alpha2: porter-layer2-eip
spec:
  selector:
    app: porter-layer2
  type: LoadBalancer
  ports:
    - name: http
      port: 80
      targetPort: 8080
  externalTrafficPolicy: Cluster

3.1.4 在KubeSphere中使用PorterLB和应用路由

由于EIP数量有限,为每个Service分配一个IP不现实,可以给Ingress分配一个IP统一暴露服务,减少EIP的使用。

通过开启KubeSphere内的项目(NameSpace)网关,KubeSphere会在kubesphere-controls-system内生成一个kubesphere-router-dev-namespace的Deployment和Service(实际上是魔改的Nginx-Ingress-Controller),我们可以通过kubectl或ks-console界面的方式设置该Service使用LB暴露出去。设置方法如下:

  1. 左侧导航栏进入项目设置下的高级设置页面,然后点击设置网关

    set-project-gateway

  2. 在弹出的对话框中选择网关的访问方式为LoadBalancer

    lb

  3. 设置网关注解中 添加两个注解:

protocol.porter.kubesphere.io/v1alpha1 值 layer2

eip.porter.kubesphere.io/v1alpha2值 porter-layer2-eip

  1. 点击保存后可以见到外部地址已被分配。

在这里插入图片描述

此时若以应用域名的方式访问10.0.0.156这个地址,则会跳转到对应的业务上。

3.2 CoreDNS 改造

每次配置业务Ingress的时候,配置nip域名不够美观,而配置自定义域名又需要改每台主机的host文件,非常的繁琐。在搭建了LB后,我们可以通过LB暴露CoreDNS,统一配置主机使用CoreDNS,省去了改host文件的麻烦。

由于CoreDNS在Kube-system NameSpace内,这部分属于kubeSphere的system-workspace,无法在图形界面上操作,只能使用Kubectl命令进行修改。

3.2.1 CoreDNS Service改造

CoreDNS Service改造非常简单,只需要增加注解以及将Tpye改为LoadBalancer即可

kubectl -n kube-system edit svc coredns
kind: Service
apiVersion: v1
metadata:
  name: coredns
  namespace: kube-system
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
    eip.porter.kubesphere.io/v1alpha2: eip-sample-pool
    k8s-app: kube-dns
    kubernetes.io/cluster-service: 'true'
    kubernetes.io/name: coredns
  annotations:
    prometheus.io/port: '9153'
    prometheus.io/scrape: 'true'
    #增加注解
    lb.kubesphere.io/v1alpha1: porter
    protocol.porter.kubesphere.io/v1alpha1: layer2
  finalizers:
    - finalizer.lb.kubesphere.io/v1alpha1
spec:
  ports:
    - name: dns
      protocol: UDP
      port: 53
      targetPort: 53
      nodePort: 32560
    - name: dns-tcp
      protocol: TCP
      port: 53
      targetPort: 53
      nodePort: 31864
    - name: metrics
      protocol: TCP
      port: 9153
      targetPort: 9153
      nodePort: 31888
  selector:
    k8s-app: kube-dns
  clusterIP: 10.233.0.3
  clusterIPs:
    - 10.233.0.3
  #类型修改
  type: LoadBalancer
  sessionAffinity: None
  externalTrafficPolicy: Cluster


3.2.2 CoreDNS ConfigMap改造

kubectl -n kube-system edit svc coredns

我们可以通过增加host和rewrite配置,在hosts插件中增加ingress配置的域名,这里以ingress为例,指向ingress.deerjoe.com 10.0.0.156地址,同时配置harbor域名rewrite至ingress。

这里仅当抛砖引玉,更多详细配置请参照CoreDNS官方文档

...
    host{
    10.0.0.156 ingress.deerjoe.com.
    fallthrough
    }
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
       ttl 30
    }

    rewrite name harbor.deerjoe.com. ingress.deerjoe.com.
...

我们也可以通过在coredns的configmap中修改Corefile,配置deerjoe.com的域名解析,并使用file插件引用配置文件。


.:53 {
    errors
    health {
       lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
       ttl 30
    }
    ### rewrite相当于CNAME的功能,当访问服务时xxx.deerjoe.com跳转到ingress.deerjoe.com
    ### 再由K8s的Ingress跳转到对应的服务

    
    prometheus :9153
    forward . /etc/resolv.conf {
       max_concurrent 1000
    }
    cache 30
    loop
    reload
    loadbalance
}
## 配置deerjoe域名访问,引用deerjoe.db文件
deerjoe.com:53  {
    errors
    health {
       lameduck 5s
    }
    file /etc/coredns/deerjoe.db deerjoe.com
    forward . /etc/resolv.conf {
       max_concurrent 1000
    }
}

同时在同个configmap中增加deerjoe.db,该文件严格按照RFC 1035-style格式编写

deerjoe.com.            IN      SOA     sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600
deerjoe.com.            IN      NS      b.iana-servers.net.
deerjoe.com.            IN      NS      a.iana-servers.net.
www.deerjoe.com.            IN      A       10.0.0.191
ingress.deerjoe.com.            IN      A       10.0.0.156
harbor.deerjoe.com.      IN      CNAME   ingress.deerjoe.com.

最终yaml为

kind: ConfigMap
apiVersion: v1
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |-
    .:53 {
        errors
        health {
           lameduck 5s
        }            
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }        
        prometheus :9153
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }

    deerjoe.com:53  {
        errors
        health {
           lameduck 5s
        }
        file /etc/coredns/deerjoe.db deerjoe.com
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
    }
  deerjoe.db: >+
    deerjoe.com.            IN      SOA     sns.dns.icann.org.
    noc.dns.icann.org. 2015082541 7200 3600 1209600 3600

    deerjoe.com.            IN      NS      b.iana-servers.net.

    deerjoe.com.            IN      NS      a.iana-servers.net.

    www.deerjoe.com.            IN      A       10.0.0.191

    ingress.deerjoe.com.            IN      A       10.0.0.156

    harbor.deerjoe.com.      IN      CNAME   ingress.deerjoe.com.


3.2.3 测试CoreDNS

通过查看coredns 的Service获取EXTERNAL-IP

kubectl -n kube-system get svc coredns

NAME      TYPE           CLUSTER-IP   EXTERNAL-IP   PORT(S)           AGE
coredns   LoadBalancer   10.233.0.3   10.0.0.157 53:32560/UDP,53:31864/TCP,9153:31888/TCP   56d

可见,CoreDNS的地址为EXTERNAL-IP地址,配置将PC主机DNS 10.0.0.157。

使用dig或ping测试harbor.deerjoe.com域名,最终指向10.0.0.156 则配置成功。

3.2.4 Pod内访问CoreDNS自定义域名

配置完上述步骤后,个人主机可以通过CoreDNS访问自定义的域名,可这个时候在任意一个pod内访问CoreDNS内自定义的域名却有一定的机率无法解析。在尝试更改CoreDNS和/etc/resolv.conf无果后,发现了Kube-system的namespace下可以看到一个名为nodelocaldns的DaemonSet。百度了一下NodeLocalDNS,

这是因为KubeSphere使用了NodeLocal DNS Cache 的方案提升CoreDNS性能和可靠性。

通过分析其挂在的configmap,有这么一段配置

...
.:53 {
    errors
    cache 30
    reload
    loop
    bind 169.254.25.10
    forward . /etc/resolv.conf  #留意该配置
    prometheus :9253
}

可见,nodelocaldns除了解析k8s集群内的DNS,剩下的请求都跳转至/etc/resolv.conf。而/etc/resolv.conf默认没有配置DNS指向coredns,自然无法解析coredns自定义的域名。那么是否在/etc/resolv.conf增加由Porter暴露出来的CoreDNS的地址就能够解析

了呢?非也,实践证明coredns 在复用宿主机*/etc/resolv.conf*时,未严格按照nameserver的顺序去解析域名,在Pod内会有一定的机率无法正确解析。

最终解决方案将nodelocaldns的configmap

修改为forward . $CoreDNS_IP 即可。

...
.:53 {
    errors
    cache 30
    reload
    loop
    bind 169.254.25.10
    #forward . /etc/resolv.conf  
    forward . $CoreDNS_IP #替换$CoreDNS_IP 为真实IP
    prometheus :9253
}

$CoreDNS_IP可以是集群内CoreDNS Service的Cluster IP或者是CoreDNS经过Loadblacer暴露出来的EIP

3.3 Cert-Manager 证书管理

由于在内网环境下,没有对外的IP,做不了域名验证,无法使用Let’s Encrypt颁发和自动更新证书,这里参照cert-manager管理内网k8s开发环境证书 一文管理自签证书。

3.3.1 安装Cert-Manager

由于Cert-Manager默认配置的Secret默认放在Cert-Manager pod所在的Namespace内,建议安装时装在对应企业空间的项目(NameSpace)内。

  1. 进入企业空间>应用管理>应用仓库>添加仓库

    在这里插入图片描述

  2. 新建仓库jetstack,地址为https://charts.jetstack.io

    在这里插入图片描述

  3. 回到项目部署Cert-Manager,并修改value.yaml配置文件

    在这里插入图片描述

  4. 配置文件中需要修改installCRDs: True,然后部署。

  5. 检查CRD是否存在,否则手动安装

    kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.crds.yaml
    

3.3.2 配置Cert-Manager

  1. 配置自签Issuer

    ### 基于Namespace
    apiVersion: cert-manager.io/v1
    kind: Issuer
    metadata:
      name: selfsigned-issuer
      namespace: dev-namespace
    spec:
      selfSigned: {}
    ---
    ### 基于集群
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: selfsigned-cluster-issuer
    spec:
      selfSigned: {}
    
  2. 创建发行者

    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: selfsigned-issuer
    spec:
      selfSigned: {}
    
  3. 创建CA证书

    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: deerjoe-selfsigned-ca
      namespace: dev-namespace
    spec:
      isCA: true
      commonName: deerjoe-selfsigned-ca
      secretName: root-secret  #CA保存的secret名
      privateKey:
        algorithm: RSA
        size: 4096
      issuerRef:
        name: selfsigned-issuer
        kind: ClusterIssuer
        group: cert-manager.io
    
  4. 创建集群CA发行者

    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: deerjoe-ca-issuer
      namespace: dev-namespace
    spec:
      ca:
        secretName: root-secret
    
  5. 创建CA发行者

    apiVersion: cert-manager.io/v1
    kind: Issuer
    metadata:
      name: deerjoe-ca-issuer
      namespace: dev-namespace
    spec:
      ca:
        secretName: root-secret
    

3.3.3 创建证书并验证

方法一:
创建通配符证书*.deerjoe.com,证书文件储存在secret:site-deerjoe-com-tls 中

# site-example-com.certificate.example-com.yaml
# 参考:https://cert-manager.io/docs/usage/certificate/
# api参考:https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1alpha3.Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: site-deerjoe-com ###
  namespace: dev-namespace ### 站点所在命名空间
spec:
  # Secret names are always required.
  secretName: site-deerjoe-com-tls ### Secret名字
  duration: 2160h # 90d
  renewBefore: 360h # 15d
  privateKey:
    algorithm: RSA
    size: 4096  
  subject:
    organizations:
    - Deerjoe Inc. ###
  commonName: "*.deerjoe.com" ###
  isCA: false
  dnsNames:
  - "*.deerjoe.com" ###
  - "deerjoe.com"
  issuerRef:
    name: deerjoe-ca-issuer ### 使用CA Issuer
    kind: Issuer ### CA Issuer
    group: cert-manager.io

在使用Ingress时直接引用secret即可。

kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: test
  namespace: dev-namespace
  annotations:
    kubesphere.io/creator: admin
    nginx.ingress.kubernetes.io/service-upstream: 'true'
spec:
  tls:
    - hosts:
        - nginx.deerjoe.com
      ### 直接引用 secret 
      secretName: site-deerjoe-com-tls
  rules:
    - host: nginx.deerjoe.com
      http:
        paths:
          - path: /
            pathType: ImplementationSpecific
            backend:
              serviceName: nginx-nc8bql
              servicePort: 80

方法二:

在Ingress中加入注解,该方法无需创建通配符证书,Cert-manager会根据ingress给出的secretName,自动签发ingress内host的证书,并写入对应的secret中。

kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: test
  namespace: dev-namespace
  annotations:
    kubesphere.io/creator: admin
    nginx.ingress.kubernetes.io/service-upstream: 'true'
    cert-manager.io/issuer: deerjoe-ca-issuer           # 告知cert-manager使用签发的issuer
    #cert-manager.io/cluster-issuer: deerjoe-ca-issuer # cluster-issuer使用这个annotations    
spec:
  tls:
    - hosts:
        - nginx.deerjoe.com
      secretName: nginx-cert # 此时的secretName为cert-manager创建并存储的secret名称.
  rules:
    - host: nginx.deerjoe.com
      http:
        paths:
          - path: /
            pathType: ImplementationSpecific
            backend:
              serviceName: nginx-nc8bql
              servicePort: 80

使用谷歌浏览器访问nginx.deerjoe.com,查看证书。

注:

  1. 留意查看生成的secret,假如secretName为nginx-cert 而NameSpace 下有一个名称为nginx-cert-xxx的secret,说明证书生成失败。请检查yaml配置的issuer或Clusterissuer是否正确,同时使用kubectl describeCertificate && kubectl describe certificaterequest 查看证书的状态。
  2. Chrome、Edge访问https无法跳过TLS认证时,请在当前页面输入“thisisunsafe”跳过,火狐不存在此问题。

3.3.4 将证书保存至本地

假设Harbor的域名为harbor.deerjoe.com,我们将dev-namespace的 site-deerjoe-com-tls证书设置为docker ssl证书。

创建文件夹

mkdir -p /etc/docker/certs.d/harbor.deerjoe.com

获取CA

kubectl -n dev-namespace get secret site-deerjoe-com-tls -ojson | jq -r '.data | ."ca.crt"' | base64 -d > /etc/docker/certs.d/harbor.deerjoe.com/ca.crt

获取tls.key

kubectl -n dev-namespace get secret site-deerjoe-com-tls -ojson | jq -r '.data | ."tls.key"' | base64 -d > /etc/docker/certs.d/harbor.deerjoe.com/harbor.deerjoe.com.key

获取tls.crt

kubectl -n dev-namespace get secret site-deerjoe-com-tls -ojson | jq -r '.data | ."tls.crt"' | base64 -d > /etc/docker/certs.d/harbor.deerjoe.com/harbor.deerjoe.com.cert

复制到docker文件夹后重启docker,并使用docker login测试

systemctl restart docker
docker login harbor.deerjoe.com

# 出现以下字样证明配置成功
#Username: admin
#Password:
#WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
#Configure a credential helper to remove this warning. See
#https://docs.docker.com/engine/reference/commandline/login/#credentials-store

#Login Succeeded
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值