Go 语言集训(一)

编程基本概念

  1. 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
  2. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
  3. 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
  4. 线程进程都是同步机制,而协程则是异步

编程基础

package Traininig


func varFunc() {
    var v1 int
    var v2 string
    var v3 [10]int //数组
    var v4 []int   // 数组切片
    var v5 struct {
        f int
    }
    var v6 *int           //指针
    var v7 map[string]int //map,key为string,value是int类型
    var v8 func(a int) int
    var (
        v1 int
        v2 string
    )

    var v1 int = 10 //正确方式1
    var v2 = 10     //正确方式2,编译器自动推导出v2的类型
    v3 := 10        //正确方式3,编译器自动推导出v3的类型

    var i int
    i := 2 //出错!

    var v10 int
    v10 = 100

    i, j = j, i

    _, _, nikcname := GetName()

    // -12
    // 3.1415926 浮点类的常量
    // 3.2+12i 复数类型的常量
    // true 布尔类型的常量
    // "foo" 字符串常量
    // -12l -12的long类型
    const Pi float64 = 3.141592658
    const zero = 0.0 //无类型浮点常量
    const (
        size int64 = 1024
        eof        = -1 //无类型整形常量
    )
    const u, v float32 = 0, 3   //u=0.0. v=3.0 常量的多赋值
    const a, b, c = 3, 4, "foo" //无类型整型和字符串常量
    const mask = 1 << 3
    const Home = os.GetEnv("HOME")

    const ( //iota被强制重置为0
        c0 = iota // c0 = 0
        c1 = iota // c1 = 1
        c2 = iota // c2 = 2
    )

    const (
        a = 1 << iota //iota被重置为0,这时候a = 1 << 0 = 1
        b = 1 << iota //b = 1 << 1 = 2
        c = 1 << iota // c = 1 << 3 = 4
    )

    const (
        u         = iota * 24 // u = 0
        v float64 = iota * 42 // v = 42.0
        w         = iota * 42 // w = 84
    )

    const x = itoa // iota 被强制重置为0
    const y = itoa // iota 被强制重置为0

    const (
        c0 = itoa // c0 = 0
        c1        // c1 = 1
        c2        // c2 = 2
    )

    const (
        a = 1 << iota // a=1
        b             // b=2
        c             // c=4
    )

    const (
        Sunday = iota
        Monday
        Tuesday
        Wendesday
        Thursday
        Friday
        Saturday
        numberofDays //这个是私有变量,包外面看不到,因为是小写的
    )

    var v1 bool
    v1 = true
    v2 := false
    v3 := (1 == 2) //v2也会被推导为bool类型

    var b bool
    b = 1       //编译错误
    b = bool(1) //编译错误 bool不支持自动或者强制的类型转换

    var value2 int32
    value1 := 32           //value1被默认推动到int类型
    value2 = value1        //编译错误,因为int和int32 在go中属于不同的类型
    value2 = int32(value1) //编译通过,强制类型转化

    mod := (5 % 3) // mod = 2

    i, j = 1, 2
    if i == j {
        fmt.Println("i and j are equal")
    }

    var i int32
    var j int64
    i, j = 1, 2
    if i == j { //编译出错,两个不同类型的整型数不能直接比较
        fmt.Println("i and j are equal")
    }
    if i == 1 || j == 2 { //编译通过,但能与字面变量(literal)进行比较
        fmt.Println("i and j are equal")
    }

    var fvalue1 float32
    fvalue1 = 12
    fvalue2 := 12.0 //必须添加.0 不然会被推导为整形,并且默认为float64,C语言中的double
    fvalue2 = float64(fvalue1)

    var value1 complex64 //由2个float32构成的复数
    value1 = 3.2 + 12i
    value2 := 3.2 + 12i
    value3 := complex(3.2, 12)

    var str string
    str = "hello world"
    ch := str[0]
    fmt.Printf("The length of \"%s\" is %d", str, len(str))
    fmt.Printf("The fist character of \"%s\" is %c.\n", str, ch)
    str[0] = "H" //编译错误

    str := "Hello,世界"
    n := len(str)
    for i := 0; i < n; i++ {
        ch := str[i]      //以字符数组的方式遍历
        fmt.Printf(i, ch) //以字节的形式输出,一共13位。UTF-8占据3个字节。byte.使用较多
    }

    for i, ch := range str { //以Unicode方式遍历
        fmt.Printf(i, ch)//以字节的形式输出,一共11位,因为每个字符类型是rune
    }

    [32]byte //define 32 byte array
    [2*N]struct {x,y int32}// 复杂类型数组
    [1000]*float64 //指针数组
    [3][5]int //二维数组
    [2][2][2]float64//等同于[2]([2]([2]float64))

    //Go语言中,数组长度在定义后就不可更改,在声明长度可以为一个常量后者一个常量表达式。常量表达式是指编译期即可计算结果的表达式
    var arr [10]int;
    arrLength := len(arr)
    }

//因为浮点数是不精确的表达,所以无法直接用==来判断两个浮点数是否相等
//采用如下方案
//p 为用户自定义的比较精度,比如0.000001
func IsEqual(f1, f2, p float64) bool {
    b = math.a
    return math.Fdim(f1, f2) < p
}

func GetName() (firstname, lastname, nickname string) {
    return "May", "Chan", "Chibi"
}

数组和数组切片

package main

import "fmt"

func modify(array [5]int) {
    array[0] = 10 //试图修改第一个元素
    fmt.Println("In modify(), array values:", array)
}
func arr() {
    array := [5]int{1, 2, 3, 4, 5}
    modify(array)
    fmt.Println("In main(),array values:", array)

    var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    var mySlice []int = myArray[5:]
    fmt.Println("Elements of Myarray:")
    for _, v := range myArray {
        fmt.Print(v, " ")
    }
    fmt.Println("\n Elements of mySlice:")
    for _, v := range mySlice {
        fmt.Print(v, " ")
    }
    fmt.Println()

    // 可动态增减元素是数组切片比数组更为强大的功能。与数组相比,数组切片多了一个存储能力。即元素个数和分配的空间可以是两个不同的值
    // 合理设置存储能力值,可以大幅度降低数组切片内部重新分配内存和搬送内存块的频率,从而大大提高程序性能

    mySlice1 := make([]int, 5, 6)
    fmt.Println("len(mySlice1)", len(mySlice1))
    fmt.Println("cap(mySlice1)", cap(mySlice1))
    mySlice1 = append(mySlice1, 1, 2)
    mySlice2 := []int{8, 9, 10, 11, 12, 14}
    mySlice2 = append(mySlice1, mySlice2[2:]...) //这里需要添加三个...,映日mySlice1都是int,而mySlice2是[]int类型的,所以需要将元素打散
    fmt.Println(mySlice2)                        //上面会自动扩充mySlice1的大小

    //基于数组切片创建数组切片
    oldSlice := []int{1, 2, 3, 4, 5}
    newSlice := oldSlice[:3] //基于oldslice的前三个元素
    fmt.Println(newSlice)

    slicea := []int{1, 2, 3, 4, 5}
    sliceb := []int{5, 4, 3}
    copy(sliceb, slicea) //智慧复制slicea的前三个元素到sliceb中
    copy(slicea, sliceb) //智慧复制sliceb的3个元素到slicea中
    fmt.Println(slicea)
}

map使用

package main

import "fmt"

type PersonInfo struct {
    ID      string
    Name    string
    Address string
}

func mapf() {
    var personDB map[string]PersonInfo     //变量申明
    personDB = make(map[string]PersonInfo) //变量创建 也可以指定大小 make(map[string] PersonInfo, 100)
    myMap := map[string]PersonInfo{
        "1234": PersonInfo{"1234", "xuan", "Room100"},
    }
    personDB["12345"] = PersonInfo{"12345", "Tome", "Room 123"}
    personDB["1"] = PersonInfo{"1", "Jack", "Room101"}
    //从map中查找键为1234的信息
    person, ok := personDB["1"]
    if ok {
        fmt.Println("Found person", person.Name, "with ID", person.ID)
    } else {
        fmt.Println("Did not find person with ID 1234.")
    }
    delete(myMap, "1234")
}

判断和控制

package main

import "fmt"

func example(x int) int {
    if x == 0 {
        fmt.Println("x==0")
        return x
    } else {
        return 1
    }
}
func main() {
    a := 1
    if a < 5 {
        fmt.Println("a<5")
    } else {
        fmt.Println("a>5")
    }
    b := example(2)
    fmt.Println(b)
    i := 2
    switch i {
    case 0:
        fmt.Printf("0")
    case 1:
        fmt.Printf("1")
    case 2:
        fallthrough //会继续执行下一个case
    case 3, 4, 5, 6:
        fmt.Printf("3,4,5,6")
    default:
        fmt.Printf("Default")
    }
    fmt.Println("\r\nno parameter in switch")
    i = 0
    switch {
    case 0 < i:
        fmt.Println(i)
    case 0 > i:
        fmt.Println("0 > i")
    }

    fmt.Println("for loop")
    sum := 0
    for {
        sum++
        if sum > 100 {
            break
        }
    }
    fmt.Println(sum)

    z := []int{1, 2, 3, 4, 5, 6, 7}
    for i, j := 0, len(z)-1; i < j; i, j = i+1, j-1 {
        z[i], z[j] = z[j], z[i]
    }
    fmt.Println(z)
    for j := 0; j < 5; j++ {
        for i := 0; i < 10; i++ {
            if i > 5 {
                break
            }
            fmt.Println(j, i)
        }
    }
}

云计算基础网络简要

ToR 交换机(Top of Rack)

每个服务器都有一个到ToR交换机的专有连接,而且ToR可以将数据转发到机架中的其他服务器,或者通过高带宽的上行端口发送到机架外部。一个典型的机架可以承载的服务器数量可达40以上,因此许多ToR交换机都有48个10Gb端口和4个连接到汇聚交换机的40Gb上行链路端口。虽然服务器带宽(480Gbit/s)和上行带宽(160Gbit/s)出现了3:1的失配,但是数据中心通信在本质上是极具突发性的,所有服务器同事完全使用10Gibts带宽是非常罕见的。
对于10Gb的端口来说,服务器和ToR交换机之间的短距离通信,可采用低成本的直连铜缆。ToR上行采用光模块,因为需要驱动更长的距离和更高的带宽才能达到其他链接多个服务器机架的交换机。
上行链路可以连接到汇聚交换机,而汇聚交换机可以将来自多个ToR交换机的通信汇聚到核心交换机。
每个ToR交换机都有一个控制平面处理器,用来配置交换机的转发表,监视交换机的健康状况等等。
ToR还可以充当服务器和网络其余部分之间的网关,提供诸如隧道、过滤、监视和负载功能。启用这些功能,需要ToR检查数据包的报头并且使用ACL规则来匹配各种报头字段。

EoR交换机(End of Row)

EoR交换机设计的目标是通过大量的交换机组件之间共享电源、冷却设备和管理基础设备来降低成本。可以认为相当于将多个ToR交换机设计为能够插入单个模块化机箱中的交换机卡。

通过取消每个ToR交换机和汇聚交换机的独立电源、冷却封山和CPU子系统可以消减成本。中央管理处理器不仅降低成本,还提供单点管理模式,不需要对多个ToR交换机单独进行管理。
配置中,每个服务器都必须通过长电缆连接到EoR交换机。缺点就是电缆长度高。

https://www.cnblogs.com/Anker/p/8998904.html

PCIe接口

PCIe接口标准多年来一直用于将外围设备连接到PC的CPU芯片组。该接口标准也用于连接CPU芯片组合服务器内的网络接口卡。存在SR-IOV(single-root IO virtualizaiton单根IO虚拟化)和MR-IOV(Multi-Root IO Virtualization,多根IO虚拟化)标准。

SR-IOV

传统PCI网卡只有一个物理功能,而SR-IOV网卡除了一个物理功能还有多个虚拟功能。每个虚拟功能都具有网卡的全部特征和功能,而且还具有自己的PCI配置空间。使得每个使用SR-IOV兼容网卡驱动程序的虚拟机在功能上像是连接了一个专用网卡。SR-IOV物理功能负责为虚拟功能连接的虚拟端口分配物理端口带宽。要实现这一点,管理程序也必须支持SR-IOV。
SR-IOV看做一种针对虚拟交换机网络连接的硬件卸载,在网卡中实现虚拟交换机网络连接是为了降低CPU利用率并改善性能
虚拟交换机可以被视作共享内存交换机,只需要在共享同一交换机的虚拟机之间传递内存指针。使用SR-IOV是,同一个服务器内两个虚拟机之间的通信从网卡返回之前必须经过网卡PCIe接口。对于当前连接到10Gb网卡的服务器而言,PCIe带宽限制在14Gbit/s左右,而虚拟机交换机可以维持30Gbit/s的传输速率。尽管比起SR-IOV,虚拟交换机需要使用更多CPU资源,但是高性能多核交换机很容易处理这种负担。

Kubernetes 集训(八):应用研发

  • 一个典型的应用minifest包含了一个或者多个Deployment和StatefulSet对象。这些对象中包含了一个或者多个容器的POD模板,每个容器都有一个存活探针和就绪探针。提供服务的POD是通过1个或者多个服务来暴露自己。当需要从集群外部访问这些服务时,需要配置LoadBalancer或者NodePort类型的服务,要么通过Ingress资源来开发服务。
  • POD模板中通常会应用两种类型的私密平局。一类是从私有镜像库拉取镜像使用的;另一种是POD中运行的进程直接来使用的。
  • 私有凭据不是应用manifest的一部分,因为他们不是有开发者来配置的,而是有运维团队来配置的。私密凭据通常会分配给ServiceAccount,然后ServiceAccount会被分配给每个单独的POD。
  • 一个应用还应该包括一个或者多个ConfigMap对象,可以用它来初始化环境变量,或者在POD中以configMap卷来挂载。也可以用emptryDir或者GitRepo卷。需要持久化的话,需要PVC卷。PVC也是应用manifest的一部分,而PVC所应用的StorageClass则是由系统管理员事先创建的。
  • 某些情况下,一个应用还需要使用Jobs和CronJos。守护进程集DaemonSet通常不是应用部署的一部分,但是通常由系统管理员创建,以在全部或者部分节点上运行系统服务。
  • HPA可以有开发者包含在应用manifest中也可以由后续运维团队添加到系统中。
  • 集群管理还会常见LiminitRange 和 ResourceQuota对象,以控制每个POD和所有POD的计算资源使用情况。

  • 应用部署后,Kuernetes控制器会自动创建其他对象,包括Endpoint,repicaset,jobs,statefulset,daemonset等时机的POD对象。

  • 资源通常通过一个或者多个标签来组织。这不仅仅适用于POD,也是用与其他资源。除了资源,大多数的资源还包含一个描述资源的注解,列出负责该资源的人员或者团队的联系信息,或者为管理者和其他的工具提供额外的元数据。

应用必须料到会被杀死或者重新调度

  1. 预料到本机IP和主机名会发生变化
  2. 预料到写入到磁盘的数据会消失
  3. 使用存储卷来跨容器持久化数据
  4. 不正常的POD,会每5分钟重启一次,在这个过程中kubenerets期望崩溃的底层原因会被解决掉。这个机制依据的基本原理是pod重新调度到其他节点通常并不会解决崩溃,因为应用运行在容器内部,所有的节点理论上

以固定顺序启动POD

运维人员应该在知道POD之间的依赖关系,按照顺序启动应用。
一个POD可以拥有任意数量的init容器。init容器是顺序执行的,并且仅当最后一个init容器执行完才会启动主容器。换句话说,init容器可以一直等待知道主容器所以来的服务启动完成并可以提供服务。当这个服务启动并且可以提供服务之后,init容器就执行结束了,然后主容器就可以启动了。
应用需要自身能够应对它所以来的服务有没有准备好的情况,配合Readiness探针。如果一个应用在其中一个依赖确实的情况下无法工作,那么他需要通过Readiness探针来通知这个情况,这样kubenretes也会知道这个而应用没有准备好。需要这样做的原因不仅仅是因为这个就绪探针收到的信号会组织应用成为一个服务端点,另外还因为deployment控制器在滚动升级的时候会使用应用的就绪探针,避免出现错误版本的出现。

增加生命周期钩子

  1. 启动后(pre-start)钩子
  2. 停止前(Pre-stop)钩子
    这个生命周期的钩子是基于每个容器来制定的,和init容器不同的是,init容器是应用整个POD。这些钩子,是在容器启动后和停止前执行器的。
    其实他们与存活探针和就绪探针相似:
  3. 在容器内执行一个命令
  4. 向一个URL发送HTTP GET请求

如果你在运行一个其他人开发的应用,大部分情况下并不想修改他的源代码。启动后钩子可以让你在不改动改应用情况下,运行一些额外的命令。这些命令可以让你在不改动应用的情况下,运行一些额外的命令。这些命令可能包括向外部监听器发送自己IDE已启动的信号,或者是初始化应用以使得应用能够顺利运行。

PreStart

这个钩子和主进程是并行执行的。钩子的名称或许有误导性,因为他并不等到主进程完全启动后才启动的。
在钩子执行完毕之前,容器一直停留在Waiting状态,其原因是contanercreating。因此pod会pending而不是running,如果钩子运行失败,主容器会被杀死。

kind:Pod
spec:
 containers:
 - image:luksa/kubia
   name: kubia
   lifcycle:
    postStart:
     exec:
       command:
       - sh
       -  -c
       - "echo 'hook will fail with exit code 15';sleep 5; exit 15"

PreStop

停止前钩子在容器被终止之前立即执行的。当一个容器需要终止运行的时候,Kubelet在配置了停止前钩子的时候就会执行这个停止前钩子,并且仅在执行完钩子程序后才会向容器进程发送SIGTERM信号。
停止前钩子在容器收到SIGTERM信号后没有优雅地关闭的时候,可以利用它来出发容器以优雅的方式管理。这些钩子在容器终止之前执行任意操作,并且并不需要应用内部实现这些操作。

lifecycle:
  preStop:
   httpGet:
    port:8080
    path: shutdown    //发送请求到http://pod_ip:8080/shudown

在应用没有收到SIGTERM信号时使用停止前钩子

许多开发者在定义停止前钩子的时候会犯错误,他们在钩子中指向应用发送了SIGTERM信号。他们这样做是因为他们没有看到他们的应用收到kubelet发送的SIGTERM信号。应用没有接收到信号的原因并不是kubernetes没有发送信号,而是因为容器内部洗你号没有被传递给应用的京城。如果你的容器镜像配置是通过执行一个shell进程,然后再shell进程内部执行应用进程,那么这个信号就被这个shell进程吞没了,这样不会传递给子进程。
在这种情况下,合理的做法是让shell进程传递这个信息给应用京城,而不是添加一个停止前钩子来发送信号给应用进程。可以通过在作为主进程执行的shell进程内处理信号并把它传递给应用进程的方式来实现。如果你无法配置容器镜像执行shell京城,而是直接通过运行应用的二进制文件,可以通过Dockerfile使用ENTRYPOINT或者CMD的exec方式来实现,即ENTROYPOINT [“/mybinary”].

POD的关闭

POD的关闭是API服务器删除POD的对象来出发的。当收到HTTP DELETE请求后,API服务器还没有删除POD对象,而是给POD设置了一个deletionTimesStamp的值。拥有这个deleteTimesStamp的POD就停止了。
Kubelet意识到需要终止POD的时候,就开始终止POD中每一个容器。Kubelet会给每个容器一定的时间来优雅地停止,这个时间叫做终止宽限期。Termination Grace Period. 每个POD单独配置。在终止进程开始之后,计时器就开始计时。:
1. 执行停止前钩子,等待执行完毕
2. 想容器的主进程发送SIGTEM信号
3. 等待容器优雅的关闭或者等待终止宽限期超时
4. 如果容器主容器没有优雅的关闭,使用SIGKILL信号终止进程。

加入应用是一个分布式数据存储,在缩容的时候,其中一个POD的实例会被删除然后关闭。在关闭过程中,这个POD是否应该在接收中职信号的时候就开始迁移数据(无论是通过SIGTERM信号还是停止前钩子)?

不是,这种做法不推荐,理由有二:
1. 一个容器终止运行并不一定代表整个POD被终止了
2. 你无法保证这个关闭流程能够在进程被杀死前执行完毕
所以应用应该通过启动关闭流程来相应SIGTERM信号,并在流程结束后终止运行。除了处理SIGTERM信号,应用还需要通过停止前钩子来收到关闭通知。在这两种情况下,应用只有固定的时间来干净地终止运行。

如果确认一个必须运行完毕的重要的关闭流程真的运行完了呢? 比如一个pod数据迁移到另外一个POD中。

一个解决方案是让应用(在接收到终止信号的时候)创建一个新的Job资源,这个Job资源会运行一个新的POD,这个POD唯一工作就是把被删除的POD数据迁移到任然存活的POD。但是注意到,你可能无法保证每个应用每次都能够成功创建这个JOB对象。万一出现故障怎么办?
用一个专门的持续运行的POD来持续检查是否存在孤立的数据。当这个POD发现孤立的数据时候,它就可以把他们迁移到仍存活的POD中。当然不一定是一个持续运行的POD,cronJob周期性运行pod也行。

但是Statefulset会有用处? 实际也不是。StatefulSet缩容时,会导致PVC孤立,这会导致存储在PVC的数据搁浅。当后续扩容时,PV会被加载到新的POD中,但是万一这个扩容操作永远不会发生,或者很久以后才发生? 所以需要使用statefulset的时候或许运行一个数据迁移的POD。为避免应用在升级过程中出现数据迁移,专门用于数据迁移的POD可以在数据迁移之前配置一个等待时间,让有状态的POD有时间运行起来。

在POD关闭时避免客户端连接断开

当API服务器接收到删除POD的请求后,首先修改ETCD状态并删除时间通知给观察者,其中两个观察者是Kubelet和端点控制器(endporint)。
事件A:当Kubelete收到POD被被终止的通知的时候,通过之前的关闭动作徐磊。如果应用立即停止接收客户端的请求以作为对SIGTEM信号的相应个,那么任何尝试链接到应用的请求都会收到Connection Refused的错误。从POD被删除到这个情况的时间相对来说短,因为API服务和kubelet是直接通信。
事假B(pod被从iptalbes规则中移除):当端点控制器收到删除通知时,他从所有pod所在服务中移除了这个pod服务端点。他通知向API服务器发送REsT请求来修改endpoint api对象。然后api会通知所有客户端关注这个endpoint对象,其中一个观察者就是运行在工作节点的kube-proxy服务。每个kube-proxy服务会在节点更新iptables规则,已组织新的链接被转发到停止的装填的POD上。移除iptables规则对已存在的链接没有影响,已经连接到pod客户端仍然可以通过这些连接向POD发送额外请求。

最后可能是,关闭POD中应用进程消耗的时间比完成iptables规则更新所需要的时间稍短,导致iptables规则更新的那一串时间相对较长,因为这些时间必须先到达endpoint,然后endpoint向API发起新请求,修改kube-proxy。存在一个很大的可能性是SIGTEM信号会在Iptalbes规则更新到所有节点之前发送了出去。 其最终的结果是,发送终止信号给POD,pod仍可以接受客户端请求。如果立即关闭服务端套接字,停止接收请求的话,会导致客户端收到“连接被拒绝”的错误。

解决问题

给你的POD添加一个就绪探针就来解决问题。POD接收到SIGTEM信号的时候,继续探针开始失败,这会导致pod从服务的端点被移除。但这个移除动作智慧在就绪探针持续失败一段时间才会发生,并且这个一处动作还是需要kubeproxy然后iptables才能移除pod。实际上,就绪探针完全不影响这个过程。端点控制器在接收到POD要被删除的通知时候,就会从endpoint中移除pod。从这个时候开始,就绪探针就没有什么作用了。肿么办呢
POD必须在接收到终止信号之后仍然保持接收连接知道所有kubeproxy完成了iptalbes规则更新。当然,不仅仅是kube-proxy,还有ingress控制器或者负载均衡直接把请求转给POD,而不经过service。这也包括使用负载均衡的客户端。为了保证不会有客户遇到连接断开的情况,需要等到他们通知你他们不会再转发请求给pod时候。但这是不可能的!因为这些组件分布在不同的机器上面。即使你知道每个组件的为主并且可以等到他们都来通知你关闭POD,万一其中一个组件未响应呢?
唯一做的合理的事情,就是等待足够长的时候,让所有kube-proxy可以完成他们的工作,多长才满足?大部分场景下,几秒就够了,但是没有办法保证每次都是足够的。当API服务器或者断电控制器过载的时候,通知到达Kube-proxy的时间会更长。你无法完美地解决这个问题,理解这个很重要。增加5-10秒延迟会极大提升用户体验

总结:
1. 等待几秒钟,然后停止接收新的连接
2. 关闭所有没有请求过来的长连接
3. 等待所有的请求完成
4. 然后完全关闭应用。

给进程终止提供更多的信息

处理应用日志

将日志或者其他文件复制到容器或者从容器复制出来
#kubectl cp foo-pod:/var/log/foo.log foo.log 
#kubectl cp localfile foo-pod:/etc/remotefile

Kubernetes 集训(七):网络安全

POD使用宿主节点的网络命名空间

hostNetwork: true

绑定宿主节点上的端口而不是用宿主节点的网络命名空间

spec.containter.hostPort:9000
与NodePort是有区别的: HostPort,到达宿主节点的端口的联机二回直接转发到POD端口。然而在NodePort服务红,到达宿主节点的端口的链接会被转发的随机的POD中。另外HostPort的POD,仅有运行了这类POD的节点会绑定对应的端口;而NodePort类型的服务会在所有的节点上绑定端口,即使这个节点上没有运行对应的POD

配置节点的安全上下文

  • 制定容器中运行进程的用户
  • 组织容器使用root用户运行
  • 使用特权模式运行容器,使其对宿主节点的内核具有完全的访问权限
  • 与以上相反,通过添加或者禁止内核功能,配置细粒度的内核访问权限
  • 设置Selinux选项,加强对容器的限制
  • 阻止进程写入容器的根文件系统

设置spec.containers.securitryContext.runAsUser: 405(指明一个用户ID)
设置spec.containers.securitryContext.runAsNonRoot: true(只允许非root运行)
设置spec.containers.securitryContext.privileged: true(允许特权模式)

为容器单独添加内核功能

Linux已经可以通过内核功能支持更细粒度的权限系统。相对于让容器运行在特权模式细啊给与其无限的权限,一个更加安全的做法是只给它始终真正需要的内核功能的权限。Kubenretes允许为特定的容器添加内核功能,或者禁用部分内核功能,以允许对容器进行更加精细的权限控制,限制攻击者潜在侵入的影响。
$kubectl exec -it pod-with-defauls — date +T% -s “12:00:00”
date:can’t set date :Operation not permitted

需要添加CAP_SYS_TIME功能
设置spec.containers.securitryContext.capabilities:add:-SYS_TIME(添加SYS_TIME功能)
!!!注意这样会导致节点不可用。

设置spec.containers.securitryContext.capabilities:drop:CHOWN(去掉CHOWN功能)
设置spec.containers.securitryContext.capabilities:readOnlyFilesystem:true(这个容器的更文件系统不允许写入)

容器使用不同用户运行时共享存储卷

Kubernetes允许为POD中所有容器制定supplemental组,以允许他们吴鸾以哪个永不ID运行都可以共享文件。可以通过设置下面两个属性:
1. fsGroup
2. supplementalGroups

PodSecurityPolicy

这是一种集群级别的资源,它定义了用户能否在POD中使用各种安全相关的特性。维护PodSecuirtyPolicy资源中配置策略的工作,由集成在API服务器中的PodSecurityPolicy准入控制插件完成。

NetworkPolicy

一个NetworkPolicy会应用在匹配他的标签选择器的POD上,指明这些允许访问这些POD的原地址,或者目的地址。并分别由ingress入和egress出指定。

在一个命名空间中启动网络隔离。创建第一个NetworkPolicy,并且podSelector为空。这会匹配所有POD,禁止任何客户端访问该民命空间的POD

允许同一命名空间的部分pod访问一个服务端POD

apiVersion: network.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: postgress-netpolicy
spec:
  podSelector:
    matchlables:
      app:database
  ingress:
  - from:
    - podSelector:
        matachlables:
          app:webserver              :只允许来自app=webserver标签的POD访问
      ports:
      - port: 5432                   :允许对这个端口访问

在不同kubernetes命名空间之间进行王路哦隔离

每个租户有多个命名空间,每个命名空间中有一个标签指明他们属于哪个租户。 如果有一个租户Manning,他的所有命名空间都有标签tenant: manning. 其中一个命名空间与西宁了shopping car,他需要允许同一租户下所有命名空间的所有pod访问。显然,其他租户禁止访问这个微服务。

kind:NetworkPolicy
metadata:
  name: shoppingcar-netporlicy
spec:
  podSelector:
    matchLabedl:
      app: shopping-cart
  ingress:
   - from:
     -namespaceSelector:
        matachLabels:
          tenant: manning
     ports:
     - port : 80

使用CIDR隔离网络

ingress.from.ipBlock.cidr: 192.168.1.0/24

限制POD对外访问流量

spec:
  podselector:
   matchlabels:
    app: webserver
  egress:
  -to:
    - podSelector:
        matachLabels:
          app: database

Kubernetes 集训(六):系统原理

控制节点:etcd,API服务,调度器,控制器管理器
工作节点:kubelet,kube-proxy,容器运行时
还有其他的:Kubenretes DNS服务器,仪表盘,ingress,heapster,网络接口插件

API服务器只做了存储资源到etcd和通知客户端有变更的工作
scheduler 调度器只给POD分配节点
controller 控制器管理确保:系统真实状态朝API服务器定义的期望的状态收敛
控制器包括:Replication管理器、RS、Deamonset、job,Node,service,endpoint,namespace,PV等控制器。控制器执行一个“调和”循环,将实际状态调整为期望状态,然后将新的实际状态写入status中。控制器利用监听机制来订阅变更,但是由于坚挺机制并不保证控制器不漏掉时间,所以任然需要定期执行重列举操作来保证不会丢到什么。
* 控制器之前并不知道彼此的存在,通过API服务器连接
*

$kubectl get componentstatus
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-0 Healthy {“health”:”true”}

通过准入控制插件验证AND、OR修改资源请求

如果请求创建、修改或者删除一个资源,请求需要经过准入控制插件的验证。服务器会配置多个准入控制插件。如果请求只是尝试读取数据,则不会做准入控制的验证。
1. AlwaysPullImages:重写pod的imagepullpolicy为always
2. serviceaccount:未明确定义服务账户的使用默认账户
3. NamespaceLifecycle: 防止在命名空间中创建正在被删除的POD,或在不存在命名空间创建POD
4. ResourceQuota:保证特定命名空间中的POD只能使用该命名空间分配数量的资源。

API服务器如何通知客户端资源变更

当创建一个RS时,它不会创建POD,同时不会去管理服务的端点。那是控制管理器的工作。

如replica控制器:由于API服务器可以通过监听机制通知客户端,那么控制器不会每次循环去查询POD,而是通过监听订阅机制可能影响期望的复制集(replicas)数量或者符合条件POD数量的变更事件。任何类型的变化,将触发控制器重新检查期望的以及实际的复制集数量,然后做出相应的操作。

Kubelet

Kubelet 负责所有运行在工作节点上内容的组件。第一个任务就是把API服务器创建一个NODE资源来注册。然后持续监控API服务器是否把该节点分配给POD,然后启动POD容器。然后Kubelet随后持续监控运行的容器,向API服务器报告他们的状态、事件和资源消耗。

Kubelet也是运行存活探针的组件,当探针报错时,会重启容器。最后,当POD从API服务器中删除时,kubelet终止容器,并通知服务器POD已经被终止。

高可用

API 服务器

通过负载均衡即可实现

etcd

etcd 设置3,5,7,个

控制器和调度器

因为控制器和调度器都会积极地监听集群装填,发生变更时做出相应操作,可能未来还会修改集群状态。当运行这些组件的多个实例时,给定时间内只能有一个实例有效。 –leader-elect 选项控制,默认为true

控制平面组件使用的领导选举机制,是采用API服务器中创建一个Endpoint资源。

安全防护

API服务器接收到的请求会经过一个认证插件列表,列表中每个插件都可以检查这个请求和尝试确定谁在发送这个请求。列表中的第一个插件可以提取请求中客户端的用户名、用户ID和组信息,并返回给API服务器。API服务器会停止调用剩余的认证插件并继续进行授权阶段。

  • 客户端证书
  • 传入HTTP头中的认证token
  • 基础的HTTP认证
  • 其他

ServiceAccount

/var/run/secrets/kubenretes.io/serviceaccount/token 每个POD都有一个serviceaccount相关联,它代表了运行在POD中应用程序的身份证明。token文件持有serviceaccount的认证token。应用程序使用这个Toekn连接API服务器时,身份认证插件会对ServiceAccount的认证token应用程序使用这个Token连接API服务器时,身份认证插件汇兑ServiceAccount进行身份认证,并将ServiceAccount的用户名传回API服务器内部。 ServiceAccount用户名的格式如下:

ssystem:serviceaccount:<namespace>:<service account name>

API服务器将这个用户名传给以配置好的授权插件,这决定该应用程序所尝试执行的操作是否被ServiceAccount允许执行。

ServiceAccount只不过是一种运行在POD中的应用程序和API服务器身份认证的一种方式

每个命名空间都有一个默认的ServiceAccount,也可以在需要时创建额外的ServiceAccount.因为集群安全性,不需要读取任何集群元数据的POD应该运行在一个受限的账户中,这个账户不许晕他们检索或者修改部署在集群中的任何资源。需要检索资源元数据的POD应该运行在只允许读取这些对象元数据的ServiceAccount下。

kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=your.email@address.com

#kubectl create role service-reader --verb=get --verb=list --resource=services -n bar
role"service-reader" created

以上是为了允许POD能够列出bar命名空间的服务。 创建了角色还不行,还需要将角色绑定到各自命名空间的serviceaccount上。
# kubectl create rolebinding test --role=service-reader --serviceaccount=foo:default -n foo
rolebinding "test" created

一个常规的角色只允许访问和角色在同一命名空间中的资源,如果虚妄允许跨不同命名空间访问资源,就必须要在每个命名空间中创建一个ROle和rolebinding。如果你想将这种行为扩展到所有命名空间则需要使用clusterRole的方式。

允许访问非资源类型的URL

API服务器也会对外暴露非资源型的URL。这些URL也必须显式地授予权限。否则,API服务器会拒绝客户端请求。

$ kubectl get clusterrole system:discovery -o yaml
对于非资源性的URL,使用普通的HTTP动词,如POST,PUT,ATTACH。而不是create/update。

访问的资源 角色类型 绑定类型
集群级别的资源Nodes,PV… ClusterRole ClusterRoleBinding
非资源类型URL(/api,/healthz…) ClusterRole ClusterRoleBinding
在任何命名空间中的资源(和跨所有命名空间的资源) clusterrole clusterrolebinding
在具体命名空间的资源(在多个命名空间中重用这个相同的clusterrole) clusterrole rolebinding
在具体命名空间中的资源(role必须在每个命名空间定义好) role rolebinding

view ClusterRole 允许对资源只读访问,但不允许查看Secretes

edit ClusterRole 允许对一个命名空间的资源修改,也允许读取和修改secret。不与匈奴查看或者修改Role和RoleBinding

admin ClusterROle 赋予一个命名空间所有控制权

cluste-admin clusterrole 得到完全的控制,也可以修改用户的ResourceQuota

Kubernetes 集训(五):有状态应用

不能通过一个RS来运行一个每个实例都需要独立存储的分布式数据存储服务,至少通过单个RS是做不到的。

为什么一些应用需要维护一个稳定的网络标识呢?

在有状态分布式应用很普遍,这类应用要求管理者在每个集群成员的配置文件中列出所有其他集群成员和他们的IP地址。但在Kubenretes,每次重新调度一个POD,这个POD就有一个新的主机名和IP,这样就要求集群中任何一个成员被重新调度后,整个应用集群都需要重新配置。

StatefulSet

StatefulSet保证了POD在重新调度后保留他们的标识和状态。他让你方便地扩容、缩容。Stateful创建的POD副本并不是完全一样的,每个POD都拥有一组独立的数据卷。并且POD的名字都是有规律的(固定的),而不是每个新POD都随机获取一个名字。

DNS SRV是DNS记录中一种,用来指定服务地址。与常见的A记录、cname不同的是,SRV中除了记录服务器的地址,还记录了服务的端口,并且可以设置每个服务地址的优先级和权重。访问服务的时候,本地的DNS resolver从DNS服务器查询到一个地址列表,根据优先级和权重,从中选取一个地址作为本次请求的目标地址。

一个statefulSet通常要求你创建一个用来记录每个POD网络标记的headlessService。通过这个service,每个POD应该有独立的DNS记录,这样集群里他的伙伴或者客户端可以直接通过主机名找到他。

当缩容一个Statefulset时,比较好的是明确哪个POD将要删除。而RS不同,不知道哪个实例会删除,也不能指定先删除哪一个。 缩容一个StatefulSet将会最先删除最高索引值的实例。因为缩容时候只会操作一个POD实例,所以缩容不会很迅速。Statefulset在有实例不健康的情况下是不允许做缩容操作的。

扩容StatefulSet增加一个副本时,会创建两个或者更多的API对象(一个POD和与之关联一个或者多个持久卷声明)。但是缩容来件个,只会删除一个POD,而留下之前创建的声明。因为数据是很重要的!

Statefulset的at-most-one语义

Kubernetes必须保证两个拥有相同标记和绑定相同持久卷声明的有状态POD实例不会同时运行。

Statefulset使用案例

  1. 创建PV 3个 均为1GB
  2. 常见一个headless Service
  3. 创建StatefulSet,其中VolumeClaimTemples中定义data使用的永久存储。其中replicas:2,设置为2个副本

POD会初始化1个,在确保第一个POD处在运行并且就绪状态时,才会创建第二个。状态明确的集群应用对同时有两个集群成员启动引起的竞争是非常敏感的

使用POD

因为创建的Service是headless模式,所以不能通过它来访问POD。需要直接连接每个POD来访问。通过API服务器作为代理。

通过API服务器与POD通信

API服务器的一个很有用的功能就是通过代理直接连接到指定的POD。

<apiserverHost>:<port>/api/v1/namespaces/default/pods/kubia-0/proxy/<path>

因为需要授权,所以通过kube proxy的方式避免授权。

对于Headless Sevice。因为没有ClusterIP,kube-proxy 并不处理此类服务,因为没有load balancing或 proxy 代理设置,在访问服务的时候回返回后端的全部的Pods IP地址,主要用于开发者自己根据pods进行负载均衡器的开发(设置了selector)。
最终还是需要创建一个ClusterIP的service,实现服务的访问。

在StatefulSet发现伙伴节点

Kubenrets的一个目标是设计功能来帮助应用完全感觉不到Kubnetes的存在。因此让应用与API服务器通信的设计是不允许的。

那怎么办呢? DNS! A,CANAME,MX记录,还有重要的SRV记录。

SRV记录用来指向提供服务的服务器的主机名和端口号 Kuberetes通过一个Headless Service创建SRV记录来指向POD的主机名。

#kubectl run -it srvlookup --image=tutum/dnsutils --rm --restart=Never -- dig SRV kubia.default.svc.cluster.local
;;  ANSWER SECTION:
k.d.s.c.l. 30  IN SRV  10 33 0 kubia-0.kubia.default.svc.cluster.local.
k.d.s.c.l. 30  IN SRV  10 33 0 kubia-1.kubia.default.svc.cluster.local.

;; ADDTIONAL SECTION:
kubia-0.kubia.default.svc.cluster.local.  30 IN  A 172.17.0.4
kubia-1.kubia.default.svc.cluster.local.  30 IN  A 172.17.0.6

当一个POD要获取一个Statefulset里的其他POD列表时,你需要做的就是触发一次SRV DNS查询。在Nodejs中查询命令为:

dns.resolveSrv("kubia.default.svc.default.cluster.local", callBackFunction);

SRV的顺序是随机的,因为他们拥有相同的优先级。但是可以设置优先级。

当一个POD处在UNKOWN的时候会发生什么?

  • 如果过段时间正常连通,并且重新汇报它上面的pod状态,那么这个POD就会被重新设置为RUnning。
  • 如果POD未知状态维持几分钟,这个POD的就会自动从节点驱逐。这是由主节点(kubenretes组件)处理的。他通过删除POD来完成它从节点上的驱逐。当Kubenlet发现这个POD被标记为删除的时候,他就开始终止运行该POD。

在确保NodeLost的节点永远不会再用,才可以尝试kubeclt delete pod kubia-o –force –grace-period 0

Kubernetes 集训(四):滚动升级

使用RC实现自动的滚动升级 kubectl rolling-update(过时)

imagePullPolicy策略依赖于镜像TAG。如果模式是latest的tag,则ImagePullPolicy默认为Always。如果指定了其他tag,则策略默认为IfNotPresent.
过时的原因:
1. kubectl rolling-update会直接修改创建的对象。直接更新POD和RC的标签并不负荷之前创建时的预期。
2. Kubectl是执行滚动升级过程中所有步骤的客户端。减少RC的副本,是由kubectl客户端执行的,而不是master执行的
3. 如果之心过程中网络连接,升级就会中断。POD和RC会处在中间状态。
4. Kubenretes是如何通过不断地收敛达到期望的系统状态。这就是POD的部署方式,以及POD伸缩方式。直接使用副本数来伸缩POD而不是通过手动删除一个pod或者增加一个pod。
5. 只需要在POD定义中更改所期望的镜像tag,并让kubenretes用运行新镜像的POD替换旧的POD,这是因为这一点,引入deployment的新资源的引入。

使用Deployment声明式地升级应用

Deployment是一种更高阶资源,用于部署应用程序,并以声明的方式升级应用,而不是通过RC或者RS。
创建Deployment之前,请确保删除扔在运行的任何RC和POD,但是暂时保留kubia-service,可以使用–all 选线来删除所有的RC。
#kubectrl create -f kubia-deployment-v1.yaml –record
deployment “kubia” created
–record 选线个,这个选项会记录历史版本号,在之后的操作中非常有用。

展示deployment滚动过程中的状态

kubectl get deployment 和 kubect descirbe deployment来产看deployment的详细信息。有一个专门用来查看部署状态:
#kubectl rollout status deployment kubia
deployment kubia successfully rolled out

RS的名称中包含POD模板的哈希值。

不同的Deployment升级策略

默认策略是执行滚动更新(Rollingupdate),另外一个粗略为Recreate,他会一次性删除所有就pod,然后创建新的POD。类似修改RC的POD模板,然后删除所有的POD。Recreate策略在删除POD之后才开始创建新的POD。这种策略,会导致应用程序出现短暂的不可用。

减慢滚动升级速度

#kubectl patch deployment kubia -p ‘{“spce”:{“minReadySeconds”:10}}’
“kubia” patched
使用patch命令更改deployment的自由属性,不会导致POD任何更新,因为POD模板没有被修改。

Kubectl set image命令来更改任何包含容器资源的镜像(RC,RS,Deployment)
#kubectl set image deployment kubia nodjs=lukia/kubia:v2
deploymnet “kubia” image updated

kubectl edit: 默认编辑器打开资源配置
Kubectl patch: 修改耽搁资源属性
kubectl apply: 通过一个完整的Yaml.json文件,应用其中新的值来修改对象。
kubectl replace:将原有对象替换为yaml/json文件中定义的新对象。这个对象必须存在
kubectl setimage:修改 POD,RC,RS,DemonSet,Job,Deployment内的镜像。

如果deployment的POD模板引用了一个ConfigMap,那么更改ConfigMap资源本身并不会触发升级操作。如果需要修改应用程序配置并相触发更新的话,可以通过创建一个新的ConfigMap并修改POD模板引用的Configmap

#kubectl rollout undo deployment kubia
deployment “kubia” rolled back
Deployment会被回滚到上一个版本

undo命令也可以在升级过程中云心,并直接停止滚动升级。

显示Deployment的滚动升级历史

kubctl rollout history deployment kubia
如果不给定–recorod参数,则这里CHNAGE-CAUSE这一栏会为空
kubectl rollout undo deployment kubia –to-version=1 ;#回滚到特定的deployment版本

控制升级速度

spec:
    strategy:
      rollingUpdate:
        maxSurge: 1
        maxUnavaiable: 0
      type: RollingUpdate

maxSurge: 决定Deployment配置中期望的副本数之外,最多允许超出的POD实例的数量。默认是25%,所以POD实例最多可以比期望多25%。 如果副本设置为4,则滚动升级过程中,不会运行超过5个POD实例。当把百分数转换为绝对值,会将数字四舍五入。这个值也可以不是百分数而是绝对值。
maxUnavaiable : 决定在滚动升级期间,相对于期间弗恩能够允许有多少POD实例处在不可用状态。默认也是25%。

金丝雀升级

kubectl rollout pause deployment kubia
kubectl rollout resume deployment kubia
设置“minReadySeconds”,减慢滚动升级速率。当所有容器就绪探针返回成功是,POD就被标记为就绪状态。如果POD出错,就绪探针返回失败。如果一个新POD运行处在,而且在minReadySeconds时间内出现了失败,那么新版本的滚动升级就被阻止。
默认在10分钟内不能完成滚动升级的话,将被视为失败。可以设置”progressDeadlineSeconds”来制定。

Kubernetes 集训(三):应用访问Kubernetes API以及内部数据

应用往往需要获取运行环境的一些信息,包括应用自身以及集群中其他组件的信息。

downward API :获取POD自身元数据,并暴露部分元数据。

Downward API允许我们通过环境变量或者文件传递pod的元数据。Downward API并不像REST endpoint那样需要通过访问的方式获取。将POD的定义和状态中取得的数据作为环境变量和文件的值。

POD mainifest: metadata, status

元数据

  • POD的名称
  • POD IP
  • POD 所在命名空间
  • POD 运行节点的名称
  • POD运行所归属的服务账户名称
  • 每个容器请求的CPU和内存的使用量
  • 每个容器可以使用的CPU和内存的限制
  • POD的标签
  • POD的注解

以上大部分都可以通过环境变量和downwardAPI卷传递给容器,但是标签和注解只能通过卷暴露。

- name : NODE_NAME
    valueFrom:
       fieldRef:
         fieldPath: spec.nodeName
- name : POD_IP
    valueFrom:
       fieldRef:
         fieldPath: status.podIP
  - name : POD_NAME
    valueFrom:
       fieldRef:
         fieldPath: metadata.name

如果更倾向于使用文件的方式而不是环境变量的方式暴露元数据,可以定义一个DownwardAPI卷并挂载到容器中。由于不能通过环境变量暴露,必须使用downwardAPI卷暴露pod标签或者注解。
可以在POD运行时修改标签和注解。但修改后,Kubernetes胡更新存有相关信息的文件,从而使POD可以获取最新的数据。而环境变量无法做到这一点。

volumes:
- name: downward
  downwardAPI:
     items:
     -path: "podName"
      fieldRef:
        fieldPath: metadata.name

因为卷的定义是基于POD级别的,而不是容器级别的,当我们引用卷定义某一个容器的资源字段时,需要明确应用容器的名称。
DownwardAPI 使得我们不比通过修改应用,或者使用sell脚本获取数据再传递给环境变量的方式来暴露数据。

与Kubenetes API交互

获取其他资源的信息或者获取最新的信息,是需要直接与API服务器直接交互。
通过kubectl prxy命令,实现代理与服务器交互 然后POD内部与API进行交互时,并没有kubectl可用。需要注意三个事情:
1. 确定API服务器的位置
2. 确保是与API服务器进行交互,而不是一个冒名者
3. 通过服务器的认证,否则将不能产看任何内容以及进行任何操作

步骤

  1. 启动一个只有curl命令的容器,通过查询环境变量或者直接curl https://kuberentes来访问API
  2. 验证服务器身份:查看secret的内容.在容器里直接执行 ls /var/run/secrets/kubernetes.io/serviceaccount/; 然后执行
    curl –cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes
    此时获得Unauthorized回复。说明我们通过CA签名,但授权没通过。需要设置CURL_CA_BUNDLE来简化操作。
    export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  3. 获取API服务授权。
    export TOKEN=/var/run/secrets/kubernetes.io/serviceaccount/token
  4. 使用curl命令
    curl -H “Authorization: Bearer $TOKEN” https://kubernetes
  • 如果使用RBAC的集群,服务账户可能不被授权访问API服务器或者部分授权,最简单的方式是运行下面的命令:
  • kubectl create clusterrolbingding permissive-binding –clusterrole=cluster-admin –group=system:serviceaccounts
  • 这个命令赋予了所有服务账户的集群管理权限,这是个危险操作。
  1. 获取POD所在的命名空间
    export NS=/var/run/secrets/kuberetes.io/serviceaccount/namespace
  2. 查看该namesapce下的资源
    curl -H “Authoriziation: Bearer $TOKEN” https://kubernetes/api/v1/namespace/$NS/pods
  3. 记性CRUD操作,对应于HTTP的方法 POST,GET,PATH/PUT,DELETE

通过Ambassador容器模式介绍

运行在主容器的应用不直接与API服务器进行交互,而是通过HTTP协议与Ambassador连接,Ambassador通过HTTPS协议连接API服务器,对应用透明地处理安全问题。这种方式同样使用了默认凭证Secret卷中的文件。

尽量使用客户端SDK

使用Swagger UI研究API

Kubernetes 集训(二):ConfigMap+Secret

ConfigMap

开发一款应用的初期,除了配置嵌入应用本身,通常会议命令行参数的形式配置应用。随着配置选项数量的组件增多,将配置文件化。
另外一种配置的传递配置选项给容器化应用程序的方法是借助环境变量。应用程序主动查找某一特定环境变量,而非读取配置文件或者解析命令行参数。
用于存储配置数据的Kubernetes资源成为ConfigMap。
绝大部分数据配置选项并未包含敏感信息,少量配置已久可能含有证书、私钥,以及其他需要保持安全的相似数据。Kubernetes提供另外一种成为secret的一级对象。

Docker 传递阐述

  • ENTRYPOINT 定义容器启动时被调用的可执行程序
  • CMD 只从传递给ENTRYPOINT的参数

经管可以直接使用CMD指令制定镜像运行时想执行的参数,正确的做法依然是借助ENTRYPOINT指令。仅仅用CMD制定所需的默认参数。这样,镜像可以直接运行,无须添加任何参数。

  • shell形式: ENTRYPOINT node app.js
  • exec形式: ENTRYPOINT [“node”,”app.js”]
    两者区别在于指定的命令是否是在shell中被调用。一般shell进程是多余的,常采用exec形式。

一般还是会用entrypoint的中括号形式作为docker 容器启动以后的默认执行命令,里面放的是不变的部分,可变部分比如命令参数可以使用cmd的形式提供默认版本,也就是run里面没有任何参数时使用的默认参数

Kubernetes中覆盖命令和参数

在Kubernetes中定义容器是,镜像的ENTRYPOINT和CMD均可以被覆盖,仅需要在容器定义中设置属性command和args的值。

ENTRYPOINT = command :容器中运行的可执行文件
CMD = args : 传给可执行文件的参数

Kubenretes允许为POD中的每一个容器都指定自定义的环境变量集合,尽管从POD层面定义环境变量同样有效,然而当前并没有提供该选项。环境变量列表无法在POD创建后被修改。

利用ConfigMap解耦配置

Kubernetes允许将配置选项分离到单独的资源对象ConfigMap中,本质上就是一个键/值对映射,值可以使短字面量,也可以是完整的配置文件。 应用无须直接读取ConfigMap,甚至不需要知道是否存在。

映射的内容通过环境变量或者卷文件的形式传递给容器,而并非直接传递给容器。

不管应用具体是如何使用ConfigMap的,将配置存放在独立的资源对象中有助于在不同环境中拥有多份同名配置清单。POD是通过名称引入ConfigMap的,因此可以在多环境下使用相同的POD定义描述,同时保持不同的配置值以适应不同环境。

# kubectl create configmap forturn-config --from-literal=sleep-interval=25 --from-literal=foo=bar --from-literal=bar=baz
configmap “forturn-config” created

#kubectl create configmap my-config --from-file=config-file.conf

给容器传递ConfigMap条目作为环境变量

  1. 传递1个环境变量。通过配置文件注入环境变量的POD。(引用不存在的ConfigMAp的容器会启动失败,其余容器能正常启动。如果之后创建了这个缺失的ConfigMap,失败容器会自动启动。如果这是configMapKeyRef.optional:true,即使config不存在,启动也能正常启动) ENV
  2. 一次传递configmap所有条目给环境变量。 可添加prefix: CONFIG_ 对合法的环境变量是可以传递的。EnvFrom
  3. 传递ConfigMap条目作为命令行参数:使用ValueFrom
  4. 使用configMap卷将条目暴露为文件:
    可以添加为文件,也可以是挂载到某一个目录下。
    spec:
        containers:
        - image: some/image
          volumeMounts:
          - name : myvolume
            mountPath : /etc/something.conf
            subpath: myconfig.conf

configmap 所有文件默认权限是644,可以通过卷规格定义中的defaultMode属性改变默认权限。

volumes:
- name: config
  configMap:
    name : fortune-config
    defaultMode : "6600"

另外可以在线更新/编辑configmap,容器内部的配置文件发生了更改,但服务却不会重新reload,还需要重新reload一遍。
挂载到已存在文件夹的文件不会被更新

这里存在一个问题,通过修改被运行容器所使用的的ConfigMap会打破容器不变性。这里关键点在与应用是否支持重载配置。ConfigMap卷重文件的更新行为对所有运行。

Secret

配置如果包含一些敏感数据,如证书和私钥,需要保证其安全性。Kubernetes提供了Secret单独资源对象。他与ConfigMap类型,均是键值对。
* Secret条目作为环境变量传送给容器
* Secret条目暴露为卷中的文件
Secret只存储在节点的内存中,永不写入物理存储,这样从节点删除secret就不需要擦除磁盘了。Secret包含三个条目
1. ca.crt
2. namespace
3. token

默认default-token secret默认会被挂载到每个容器。可以设定pod定义的automountServiceAccountToken字段为false,或者设置pod使用的服务账户中相同字段为false来关闭默认行为。
Secret条目都是被Base64格式编码,而Configmap直接用纯文本展示。在处理YAML和JSOn格式的secret时有所麻烦,需要在设置和读取相关条目时对内容进行编解码。
Secret有大小限制,最大为1MB。
Secret卷存储于内存中:使用tmpfs,存储在secret的数据不会写入磁盘。无法被窃取。

使用Secret环境变量的坏处是,当出现错误等异常行为时,会打印log日志,一般会打印环境变量等信息,造成信息暴露

使用secret,从私有镜像库拉取镜像。

# kubectl create secrete docker-registry mydockerhubsecret --docker-username=myusername --docker-password=mypassword --docker-email=my.email@provider.com

pod-with-private-image.yaml
    kind:pod
    apiVersion: v1
    spec:
       imagePullSecrets:
       -name: mydockerhubsecret
       containers:
       -image: username/private:tag
        name: main

可通过添加secret到serviceaccount,使所有pod都能够自动添加上镜像的secret.