August 16, 2021
k8s安装负载均衡器:Metallb
在使用kubenetes的过程中,如何将服务开放到集群外部访问是一个重要的问题。当使用云平台(阿里云、腾讯云、AWS等)的容器服务时,我们可以通过配置 service 为 LoadBalancer 模式来绑定云平台的负载均衡器,从而实现外网的访问。但是,如果对于自建的 kubernetes裸机集群,这个问题则要麻烦的多。
祼机集群不支持负载均衡的方式,可用的不外乎NodePort、HostNetwork、ExternalIPs等方式来实现外部访问。但这些方式并不完美,他们或多或少都存在的一些缺点,这使得裸机集群成为Kubernetes生态系统中的二等公民。
MetalLB 旨在通过提供与标准网络设备集成的Network LB实施来解决这个痛点,从而使裸机群集上的外部服务也尽可能“正常运行”,减少运维上的管理成本。它是一种纯软件的解决方案,参考 https://kubernetes.github.io/ingress-nginx/deploy/baremetal/。
从 v0.13.0 版本开始,官方对解决方案进行了部分调整,操作步骤简洁一些,建议使用最新版本,参考官方教程 https://metallb.universe.tf/installation/
部署 创建namespace $ kubectl create namespace metallb-system 新建 secret $ kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)" 参数 from 前面是两个-
部署 root@sxf-virtual-machine:~# kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ podsecuritypolicy.policy/controller created podsecuritypolicy.policy/speaker created serviceaccount/controller created serviceaccount/speaker created clusterrole.rbac.authorization.k8s.io/metallb-system:controller created clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created role.rbac.authorization.k8s.io/config-watcher created role.rbac.authorization.k8s.io/pod-lister created role.
August 7, 2021
服务网格Istio之服务入口 ServiceEntry
使用服务入口(Service Entry) 来添加一个服务入口到 Istio 内部维护的服务注册中心。添加了服务入口后,Envoy 代理可以向服务发送流量,就好像它是网格内部的服务一样,可参考 https://istio.io/latest/zh/docs/concepts/traffic-management/#service-entries。
简单的理解就是允许内网向外网服务发送流量请求,但你可能会说正常情况下在pod里也是可以访问外网的,这两者有什么区别呢?
确实默认情况下,Istio 配置 Envoy 代理可以将请求传递给外部服务。但是无法使用 Istio 的特性来控制没有在网格中注册的目标流量。这也正是 ServiceEntry 真正发挥的作用,通过配置服务入口允许您管理运行在网格外的服务的流量。
此外,可以配置虚拟服务和目标规则,以更精细的方式控制到服务条目的流量,就像为网格中的其他任何服务配置流量一样。
为了更好的理解这一块的内容,我们先看一下普通POD发送请求的流程图普通 Pod 请求
创建 ServiceEntry 资源 举例来说:
svc-entry.yaml
apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: svc-entry spec: hosts: - "www.baidu.com" ports: - number: 80 name: http protocol: HTTP - number: 443 name: https protocol: HTTPS location: MESH_EXTERNAL resolution: DNS 该 ServiceEntry 资源定义了一个外部网站 www.baidu.com 服务,并将它纳入到 Istio 内部维护的服务注册表(服务网格)中。
创建资源
$ kubectl apply -f svc-entry.yaml serviceentry.networking.istio.io/svc-entry created 现在我们已经创建了一个 ServiceEntry, 默认在 default 命名空间。
July 31, 2021
在linux下安装Kubernetes
环境 ubuntu18.04 64位
Kubernetes v1.21.3
这里需要注意,本教程安装的k8s版本号 <- v1.24.0,主要是因为从v1.24.0以后移除了 Dockershim,无法继续使用 Docker Engine,后续将默认采用 containerd ,它是一个从 CNCF 毕业的项目。如果仍想使用原来 Docker Engine 的方式可以安装 cri-dockerd ,它是 Dockershim 的替代品。
如果你想将现在 Docker Engine 的容器更换为 containerd,可以参考官方迁移教程 将节点上的容器运行时从 Docker Engine 改为 containerd
为了解决国内访问一些国外网站慢的问题,本文使用了国内阿里云的镜像。
更换apt包源 这里使用aliyun镜像 , 为了安全起见,建议备份原来系统默认的 /etc/apt/sources.list 文件
编辑文件 /etc/apt/sources.list,将默认网址 或 替换为
更新缓存
$ sudo apt-get clean all $ sudo apt-get update 安装Docker 参考官方文档 或 aliyun 文档
安装 docker 安装基础工具 $ sudo apt update $ sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release 安装GPG证书 $ curl -fsSL https://download.
June 27, 2021
SPIFFE 学习参考资料
The SPIFFE Workload API
Envoy spiffe spire
简而言之SPIFFE
SPIFFE信任域
使用SPIRE(自动)提供TLS证书给Envoy以进行更强大的身份验证
谁使用SPIFFE?
Securing the Service Mesh with SPIRE 0.3
May 23, 2021
Golang中的runtime.LockOSThread 和 runtime.UnlockOSThread
在runtime中有 [runtime.LockOSThread](https://github.com/golang/go/blob/go1.16.3/src/runtime/proc.go#L4248-L4278) 和 [runtime.UnlockOSThread](https://github.com/golang/go/blob/go1.16.3/src/runtime/proc.go#L4302-L4323) 两个函数,这两个函数有什么作用呢?我们看一下标准库中对它们的解释。
runtime.LockOSThread // LockOSThread wires the calling goroutine to its current operating system thread. // The calling goroutine will always execute in that thread, // and no other goroutine will execute in it, // until the calling goroutine has made as many calls to // UnlockOSThread as to LockOSThread. // If the calling goroutine exits without unlocking the thread, // the thread will be terminated. // // All init functions are run on the startup thread.
May 21, 2021
认识无锁队列
无锁队列是 lock-free 中最基本的数据结构,一般应用在需要一款高性能队列的场景下。
对于多线程用户来说,无锁队列的入队和出队操作是线程安全的,不用再加锁控制
什么是无锁队列 队列每个开发者都知道,那么什么又是无锁队列呢?字面理解起来就是一个无锁状态的队列,多个线程(消费者)同时操作数据的时候不需要加锁,因为加/解锁都是一个很消耗资源的动作。
实现原理 我们先看一下无锁队列的底层实现数据结构。
数据结构 无锁队列底层的数据结构实现方式主要有两种:数组 和 链接。
数组 在首次初始化时,需要申请一块连接的大的内存。读写数据直接从数据的指定位置操作即可,时间复杂度为O(1)。
缺点:数组长度有限,一旦数组索引位置写满,则无法继续写入,即队列有上限。
链表 不用像数组一样,刚开始就申请一块连接的大的内存空间。只有在每次写时数据的时候,申请这个数据节点大小的内存即可,这样就可以实现无限的写入,没有长度限制问题。
缺点:每次写数据都要申请内存,在写的场景,最差的情况是多少个数据就申请多少次内存,而每次申请都是一个消耗资源的动作。
可以看到无锁底层的实现的不同各有优势。多数据情况下,我们都采用链表来实现无锁队列,主要原因就是写入可以没有长度的限制。相比每次申请都要费时来说,满足前面的条件是我们最基本的要求。当然主要还是真正的使用场景。
CAS CAS 是 Compare And Swap 的简称, 属于 乐观锁,这是一个并发同步原语. 伪代码如下:
bool compare_and_swap(int *reg, int oldval, int newval) { int reg_val = *reg; if(reg_val == oldval) { *reg = newval; return true; } return false; } CAS操作有三个参数,分别表示 内存值V、旧的预期值A 和 修改后的更新值B。
判断变量内存某个位置的值是否为预期值,如果是则更改为新的值,并返回true,这个过程是原子性操作。如果修改失败,可能需要重试再次执行CAS操作,直到修改成功,一般称此过程为自旋。可以看到每次调用 CAS 命令前需要先读取旧值 oldval。
现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是 CMPXCHG 汇编指令。有了这个操作,我们就可以用其来实现各种无锁的数据结构。
使用场景 无锁队列也属于队列的一种,所以大部分队列的使用场景都可以使用它来代替其它有锁队列,无锁队列通过不加锁的方式提高队列性能。
参考资料 https://zhuanlan.zhihu.com/p/336912752 无锁队列是否不适用于大容量应用场景? https://www.
May 10, 2021
Runtime: goroutine的暂停和恢复源码剖析
上一节《 GC 对根对象扫描实现的源码分析》中,我们提到过在GC的时候,在对一些goroutine 栈进行扫描时,会在其扫描前触发 G 的暂停([suspendG](https://github.com/golang/go/blob/go1.16.2/src/runtime/preempt.go#L76-L254))和恢复([resumeG](https://github.com/golang/go/blob/go1.16.2/src/runtime/preempt.go#L256-L280))。
// markroot scans the i'th root. // // Preemption must be disabled (because this uses a gcWork). // // nowritebarrier is only advisory here. // //go:nowritebarrier func markroot(gcw *gcWork, i uint32) { baseFlushCache := uint32(fixedRootCount) baseData := baseFlushCache + uint32(work.nFlushCacheRoots) baseBSS := baseData + uint32(work.nDataRoots) baseSpans := baseBSS + uint32(work.nBSSRoots) baseStacks := baseSpans + uint32(work.nSpanRoots) end := baseStacks + uint32(work.nStackRoots) // Note: if you add a case here, please also update heapdump.
May 7, 2021
goroutine栈的申请与释放
对于提高对 stack 的使用效率,避免重复从heap中分配与释放,对其使用了 pool 的概念,runtime 里为共提供了两个pool, 分别为 stackpool ,另一个为 stackLarge。stack pool
stackpool: 16b~32k 对应通用的大小的stack。获取时通过调用 stackpoolalloc(), 释放时调用 stackpoolfree()。
stackLarge:对应 > 32K 的 stack
在程序全局调度器 初始化 时会通过调用 stackinit() 实现对 stack 初始化。
当我们执行一个 go func() 语句的时候,runtime 会通过调用 newproc() 函数来创建G。而内部真正创建G的函数为 [newproc1()](https://github.com/golang/go/blob/go1.16.3/src/runtime/proc.go#L3990-L4098),在没有G可以复用的情况下,会通过 newg = malg(_StackMin) 语句创建一个包含stack的G。
// Allocate a new g, with a stack big enough for stacksize bytes. func malg(stacksize int32) *g { newg := new(g) if stacksize >= 0 { stacksize = round2(_StackSystem + stacksize) systemstack(func() { newg.
May 7, 2021
Golang的GPM 模型在网络编程中存在的问题
现状 目前在网络编程中,golang采用的是一种 goroutine-per-connection 的模式,即为每一个连接都分配一个goroutine,一个连接就是一个goroutine,多个连接之间没有关系。
package main import ( "fmt" "io/ioutil" "net" "time" ) //模拟server端 func main() { tcpServer, _ := net.ResolveTCPAddr("tcp4", ":8080") listener, _ := net.ListenTCP("tcp", tcpServer) for { //当有新客户端请求时拿到与客户端的连接 conn, err := listener.Accept() if err != nil { fmt.Println(err) continue } // 处理逻辑 goroutine-per-connection go handle(conn) } } func handle(conn net.Conn) { defer conn.Close() //读取客户端传送的消息 go func() { response, _ := ioutil.ReadAll(conn) fmt.Println(string(response)) }() //向客户端发送消息 time.Sleep(1 * time.Second) now := time.
April 30, 2021
缓存池 bytebufferpool 库实现原理
上一节 《Runtime: Golang 之 sync.Pool 源码分析》 我们介绍了sync.Pool 的源码分析,本节介绍一个 fasthttp 中引用的一缓存池库 [bytebufferpool](https://github.com/valyala/bytebufferpool),这两个库是同一个开发者。对于这个缓存池库与同类型的几个库的对比,可以参考 https://omgnull.github.io/go-benchmark/buffer/。
建议大家了解一下[fasthttp](https://github.com/valyala/fasthttp) 这个库,性能要比直接使用内置的 net/http 高出很多,其主要原因是大量的用到了缓存池 sync.Pool 进行性能提升。
用法 // https://github.com/valyala/bytebufferpool/blob/18533face0/bytebuffer_example_test.go package bytebufferpool_test import ( "fmt" "github.com/valyala/bytebufferpool" ) func ExampleByteBuffer() { // 从缓存池取 Get() bb := bytebufferpool.Get() // 用法 bb.WriteString("first linen") bb.Write([]byte("second linen")) bb.B = append(bb.B, "third linen"...) fmt.Printf("bytebuffer contents=%q", bb.B) // 使用完毕,放回缓存池 Put() // It is safe to release byte buffer now, since it is no longer used. bytebufferpool.Put(bb) } 全局变量 我们先看一下与其相关的一些常量