搭建k8s的Dashboard服务

前言

这篇文章记录下自己搭建一个Kubernetes Dashboard的过程。

什么是Kubernetes Dashboard

Kubernetes Dashboard是一套基于Web的通用UI,可以允许用户对集群中运行的应用进行管理以及故障修复,还可以管理集群本身。安装完k8s后,大部分操作都是基于kubectl命令来操作的,使用Kubernetes Dashboard可以更方便的管理集群。

怎么搭建Kubernetes Dashboard

搭建Kubernetes Dashboard 大致需要以下几个步骤:

  1. 生成Https证书
  2. 创建认证Token
  3. 部署服务
  4. 获取认证Token
  5. 访问登录

生成Https证书

mkdir certs
openssl req -nodes -newkey rsa:2048 -keyout certs/dashboard.key -out certs/dashboard.csr -subj "/C=/ST=/L=/O=/OU=/CN=kubernetes-dashboard"
openssl x509 -req -sha256 -days 10000 -in certs/dashboard.csr -signkey certs/dashboard.key -out certs/dashboard.crt

第一行命令openssl req 创建新的PKCS#10格式证书请求和新私钥。其中dashboard.key是私钥,dashboard.csr是证书请求。所谓的CSR就是Certificate Signing Request。

第二行命令,openssl x509 对上面生成的csr,用dashboard.key私钥自签名,生成dashboard.crt。所谓的CRT就是certificate的缩写,即证书。

创建认证Token

官方wiki里,推荐使用https的链接来访问Dashboard。默认情况下会生成自签名证书并将其存储在内存中。如果要自定义证书,就需要按照下面的命令操作。自定义证书必须存储在命名 空间kubernetes-dashboard-certskube-system命名的机密中。假设你有dashboard.crtdashboard.key文件存储在$HOME/certs目录下,你应该用这些文件的内容创建Token,该命令如下:

kubectl create secret generic kubernetes-dashboard-certs --from-file=$HOME/certs -n kube-system

部署服务

根据kubernetes-dashboard-lb.yaml 文件来部署服务,详细配置文件如下:

---
# ------------------- Dashboard Service Account ------------------- #

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Role & Role Binding ------------------- #

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
rules:
  # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["create"]
  # Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["create"]
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs"]
  verbs: ["get", "update", "delete"]
  # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["kubernetes-dashboard-settings"]
  verbs: ["get", "update"]
  # Allow Dashboard to get metrics from heapster.
- apiGroups: [""]
  resources: ["services"]
  resourceNames: ["heapster"]
  verbs: ["proxy"]
- apiGroups: [""]
  resources: ["services/proxy"]
  resourceNames: ["heapster", "http:heapster:", "https:heapster:"]
  verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard-minimal
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Deployment ------------------- #

kind: Deployment
apiVersion: apps/v1beta2
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
      - name: kubernetes-dashboard
        image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1
        ports:
        - containerPort: 8443
          protocol: TCP
        args:
          - --auto-generate-certificates
          # Uncomment the following line to manually specify Kubernetes API server Host
          # If not specified, Dashboard will attempt to auto discover the API server and connect
          # to it. Uncomment only if the default does not work.
          # - --apiserver-host=http://my-address:port
        volumeMounts:
        - name: kubernetes-dashboard-certs
          mountPath: /certs
          # Create on-disk volume to store exec logs
        - mountPath: /tmp
          name: tmp-volume
        livenessProbe:
          httpGet:
            scheme: HTTPS
            path: /
            port: 8443
          initialDelaySeconds: 30
          timeoutSeconds: 30
      volumes:
      - name: kubernetes-dashboard-certs
        secret:
          secretName: kubernetes-dashboard-certs
      - name: tmp-volume
        emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule

---
# ------------------- Dashboard Service ------------------- #

kind: Service
apiVersion: v1
metadata:
  annotations:
    service.kubernetes.io/qcloud-loadbalancer-clusterid: cls-xxxxxx
    service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-8uouk8f0
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  type: LoadBalancer
  ports:
    - port: 443
      targetPort: 8443
  selector:
    k8s-app: kubernetes-dashboard

这个配置文件主要定义创建账户,绑定对应的角色以及如何部署该服务:

  1. 服务账户 Dashboard Service Account
  2. 账户角色的绑定 Dashboard Role & Role Binding
  3. 服务的部署 Dashboard Deployment

部署的命令如下:

CLUSTER_ID=$(cat /etc/kubernetes/config | grep KUBE_CLUSTER | awk -F '"' '{print $2}')
sed -i "154s/cls-[a-z0-9]*/${CLUSTER_ID}/" kubernetes-dashboard-lb.yaml
kubectl create -f kubernetes-dashboard-lb.yaml

这一行命令是这样运行的:

  1. 从/etc/kubernetes/config的配置文件,获取KUBE_CLUSTER变量值,使用awk流编辑器,以双引号作为字段分隔符,获取第二列的值保存到临时变量CLUSTER_ID
  2. 使用sed 编辑替换第154行的cls-后面字符为当前获取到CLUSTER_ID变量值。
  3. kubectl 对该配置文件进行部署。

获取认证Token

通过以下命令,检查部署服务的状态,即name为Kubernetes-dashboard开头的服务status为Running。

kubectl get pod -n kube-system

这一行命令是这样获取资源信息的:

  1. kubectl get 列举应用实例的信息
  2. -n 参数指定namespace 为kube-system

按照以下配置文件admin-role.yaml进行部署:

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: admin
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: admin
  namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin
  namespace: kube-system
  labels:
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile

执行部署命令:

kubectl create -f admin-role.yaml

获取认证Token:

kubectl -n kube-system describe secret admin-token

这一行命令,从命名空间kube-system获取描述信息,复制记录token: 后的字符串,方便后面登录Dashboard。

访问登录

执行以下命令获取访问地址:

kubectl get service kubernetes-dashboard -n kube-system

从EXTERNAL-IP获取到对外可访问的ip地址,输入浏览器https://ip即可访问。访问成功后,选择Token登录即可。这样我们就成功部署了Kubernetes Dashboard服务了。

总结

这篇文章介绍如何搭建Kubernetes Dashboard服务,这仅仅是学习Kubernetes的第一步,使用Kubernetes Dashboard服务可以更好的管理你的集群。K8s的架构设计可能要复杂得多,一篇文章可能还无法窥见全貌,希望通过搭建Dashboard服务可以更好的了解学习K8s服务。

K8s必知必会

前言

Kubernetes是用于自动部署,扩展和容器花应用程序的开源系统,简单的来讲就是容器集群调度平台,可以自动化应用容器的部署、扩展和操作,提供以容器为中心的基础架构。

使用 Kubernetes, 您可以快速高效地响应客户需求:

  • 快速、可预测地部署您的应用程序
  • 拥有即时扩展应用程序的能力
  • 不影响现有业务的情况下,无缝地发布新功能
  • 优化硬件资源,降低成本

应用场景

我们先看下k8s的官方对应用场景的定义。

Kubernetes是Google基于Borg开源的容器编排调度引擎,作为CNCF(Cloud Native Computing Foundation)最重要的组件之一,它的目标不仅仅是一个编排系统,而是提供一个规范,可以让你来描述集群的架构,定义服务的最终状态,Kubernetes可以帮你将系统自动地达到和维持在这个状态。Kubernetes作为云原生应用的基石,相当于一个云操作系统,其重要性不言而喻。

这些定义是什么鬼?为什么每个字我都认识,但就不明白是啥意思?

来,我们先理解下什么是云?这里的云,肯定不是天上飘的那种,我们说的云,其实就是云服务。我还是先去翻翻维基百科吧。云的定义是这样子的:

云计算(英语:cloud computing),是一种基于互联网的计算方式,通过这种方式,共享的软硬件资源和信息可以按需求提供给计算机各种终端和其他设备。

所谓的云就是,以互联网的方式提供服务器端的计算服务。在云上,你可以运行很多自定义的服务,而不需要关心基础设施的细节,简单的来讲,你不需要自己购买维护服务器硬件了,因为你可以买云提供商已经部署的服务器,直接用就可以了。

那么问题来了,虽然不需要管理维护服务器硬件了,但我们运行部署的自定义服务,还是得自己维护吧。这时候,K8s就站出来说,它可以帮你管理运行的应用,不管你的应用啥时候挂了,还是需要扩容,它都可以帮你搞定。只要你描述了整个集群的架构是怎么样的,定义你的服务最终状态,K8s都可以很好管理和维护服务的状态。那K8s到底是怎么做到的呢?

组成部分

K8s的主要组成部分有3个,分别为:Master、Node、Pods。

Master

负责集群内所有pods的状态,负载均衡等,主要组件有Ectd、Controller Manager、API Server、Scheduler等。

  1. 采用Etcd键值对服务,保存了整个集群的状态。
  2. Controller Manager负责维护集群的状态,比如故障检测、自动扩展、滚动更新等
  3. API Server提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制
  4. Scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上

Node

这些Node和我们经常遇到的节点,其实就是一回事。这些都是真实的物理机器,连接了网络,有CPU计算能力。主要组成有:

  1. kubelet负责维护容器的生命周期,同时也负责Volume(CSI)和网络(CNI)的管理

  2. Container runtime负责镜像管理以及Pod和容器的真正运行(CRI)

  3. kube-proxy负责为Service提供cluster内部的服务发现和负载均衡

再往下走,就涉及到主机的操作系统OS,底层硬件了。

Pods

这是整个架构中的精华部分。

对节点抽象出来的一层,节点的操作系统,硬件部分可能会有不同的差异,通过在Docker+Kubelet+kuebe-proxy的基础上抽象出一个集装箱层,类似与编程中对业务逻辑的抽象,也就是Pod.

这一层关注的是标准化,提供集装箱Pod的格式给到业务,至于里面的应用,只要符合Pod的标准,都可以正常运行起来,并且还是支持自动部署,自动恢复,负载均衡等。

关键点

Kubernetes设计理念和功能其实就是一个类似Linux的分层架构。

分类 实现 备注
核心层 Kubernetes最核心的功能,对外提供API构建高层的应用,对内提供插件式应用执行环境
服务层 部署服务(无状态应用、有状态应用、批处理任务、集群应用等)
路由服务(服务发现、DNS解析等)
Service Mesh服务(部分位于应用层)
管理服务:系统度量(如基础设施、容器和网络的度量),自动化(如自动扩展、动态Provision等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy等)、Service Mesh(部分位于管理层)
接口服务:kubectl命令行工具、客户端SDK以及集群联邦
应用层 也就是可以运行的生态系统服务
Kubernetes外部:日志、监控、配置管理、CI/CD、Workflow、FaaS、OTS应用、ChatOps、GitOps、SecOps等
Kubernetes内部:CRICNICSI、镜像仓库、Cloud Provider、集群自身的配置和管理等

总结

文章中,我们了解到云的定义,K8s的应用场景,以及K8s的常用组件,下面,我们尝试搭建K8s组件Dashborad,方便管理集群。

参考链接

  1. Kubernetes架构

为什么你需要写单元测试?

前言

维基百科中对单元测试的定义是这样的:

计算机编程中,单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

但在日常开发中,大多数情况下,可能开发需求都排得满满的,哪还有时间去写单元测试?在我工作的前几年,基本上也没有写单元测试。单元测试只是学习了解而已。直到,我遇到了一个由于bug引起的重大事故。事后的反思,让我开始关注到代码质量的问题。

有没有办法提前发现这类问题?如何防止这类问题再次发生?如何提升代码质量?

面对上面的灵魂拷问,我对如何写好代码进行深刻的反思。写完代码,并不代表,你已经做完了,做好了,代码质量才是程序员的核心竞争力。测试通过的代码,才能有信心交付。单元测试才是有效提升代码质量的最简单可靠的方式。

为什么要写单元测试

对自己的代码有信心

修改代码后通过单元测试,说明修改的部分并没有破坏测试用例,这能增加对代码的交付信心。起码你有底气交给测试的同学去测试你的代码。但也要了解,通过单元测试并不意味着没有问题,有一些bug并不是单元测试的问题。单元测试关注的一个小的单元,整体功能的完整性并不能通过单元测试代替集成测试。

代码重构

很多时候,写业务代码,时间有很紧张,想要日后优化重构的话,没有写单元测试用例,根本就是寸步难行。很可能重构后,代码会不小心破坏掉之前填的坑。所以开发一般不会去重构现有的代码。但如果有单元测试的话,就不一样了,对现有的功能重构,建立对应的测试用例,改完代码,跑一边单元测试,如果测试都通过了,那么就代表重构并没有破坏原有的逻辑正确性。只有单元测试质量高,覆盖率高,基本上你可以随时重构优化你的代码。当然,单元测试依然适用于小的单元,重构一个函数,一个类,都是可以的。比如将当前的项目重构程为微服务,这时可能就需要重写单元测试了。

熟悉代码

单元测试通常会包含较多业务异常的测试用例,通过单元测试可以了解到需要注意的点,哪些业务是如何运行的,你不需要深入阅读代码就可以了解代码是做什么的,也是一个熟悉业务和代码的好办法。

怎么写单元测试

通常情况下,我们都是在已有的项目下进行单元测试的编写。单元测试都会有对应开发框架,以PHP为例,我推荐PHPunit,它是面向程序员的单元测试框架。在已有的项目加入phpunit,很简单。你需要以下3个步骤:

  1. 在项目加入配置文件phpunit.xml
  2. 安装好composer,并添加phpunit依赖
  3. 添加自定义引导文件

phpunit.xml

通常phpunit.xml的模板如下:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="cases/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./cases/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">cases/</directory>
        </whitelist>
    </filter>
    <logging>
        <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/>
    </logging>
</phpunit>

常见选项参数解释:

  1. bootstrap 定义你的引导文件,通常是你的框架加载类。
  2. colors 为true 可在终端显示颜色
  3. convertErrorsToExceptions 转换Fatal Error为异常
  4. convertErrorsToExceptions 转换注意Notice为异常
  5. convertWarningsToExceptions 转换警告warn为异常
  6. processIsolation 为每个测试运行在单独的进程增加隔离
  7. stopOnFailure 出现失败后是否停止运行测试用例
  8. testsuites->testsuite 通过name参数区分不同的测试用例,需要配置搜索用例的目录
  9. filter->whitelist 配置代码覆盖率的白名单
  10. logging->log->coverage-text 配置代码覆盖率的输出为php 标准输出,不显示白名单文件

composer

通常安装composer很简单

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

通过composer安装phpunit

#php7.0 - 7.2
composer require --dev phpunit/phpunit ^6
#php7.1 - 7.3
composer require --dev phpunit/phpunit ^7
#php7.2 - 7.4
composer require --dev phpunit/phpunit ^8

引导文件

有几种方式添加你的引导文件,第一种直接在命令行添加:

phpunit --bootstrap cases/autoload.php

第二种,直接在phpunit.xml配置参数

bootstrap="cases/autoload.php"

该文件用于加载常用的框架,公用库,函数等。

TDD

在日常工作中,使用单元测试的工作方式有3类:

  1. 从一开始就编写对应测试用例,也就是测试驱动开发,这其实就是TDD。
  2. 第二种是,先编写部分的代码,然后再编写对应的测试用例。
  3. 第三种,编写完代码,再编写测试用例。
分类 优点 不足 备注
TDD 不需要了解小单元的业务逻辑 关注点更多是在测试上,无法深入了解业务逻辑
T&C(Test & Code) 实时性强,写完一段代码就运行下测试,通过测试很有成就感 实现的同时就需要考虑,如何更好的测试复用
TAC(Test After Code) 符合日常开发流程 忘记写测试用例,重新梳理思路

实践经验

在实践单元测试的过程中,有一些经验可以供你参考。

什么时候需要写单元测试

如果每个函数方法都去写单元测试的话,时间可能根本来不及。建议以下代码进行单元测试:

  1. 核心业务逻辑。阅读、充值、购买等。
  2. 逻辑比较复杂的。
  3. 容易出错。
  4. 不容易理解的代码。
  5. 公共库文件。

测试用例

最好的情况下,是把测试用例与代码一起管理,这样可以方便阅读测试与被测试的代码。在进行代码部署时,可以不需要发布测试用例。如果想要做持续集成,也可以单独管理测试用例。建议的目录以模块进行组织,避免出现一个文件几千条测试用例的混乱情况。下面是一个参考的目录结构:

➜  tests git:(master) tree
.
├── bin
│   ├── release.sh // 发布脚本
├── cases
│   ├── APITest.php // 单元测试基类
│   ├── AppTest.php // App帮助函数
│   ├── autoload.php // 自动加载框架
│   ├── modulea // 模块a
│   ├── moduleb // 模块b
│   ├── modulec // 模块c
│   ├── moduled // 模块d
│   ├── modulee // 模块e
│   └── modulef // 模块f
├── composer.json // composer加载配置文件
├── composer.lock // composer锁文件,锁定安装时的版本
├── phpunit.xml // phpunit单元测试配置文件
└── README.md //单元测试文档

经验之谈

  1. 单元测试不是越多越好,而是越有效越好!
  2. 单元测试,最好是与实现代码同时进行。
  3. 单元测试需要覆盖核心业务逻辑等。

总结

在这篇文章,总结了从自己不会写单元测试到开始学习单元测试的经验,对比了3类单元测试的开发方式,分享了单元测试的实践经验。如果可能的话,可以将单元测试加入到日常的持续集成中,大大提高构建出来的代码质量。

程序员使用单元测试是提升代码质量最简单可靠的方式。