k8s中的Service与Ingress

集群中的服务要想向外提供服务,就不得不提到Service和Ingress。 下面我们就介绍一下两者的区别和关系。

Service

必须了解的一点是 Service 的访问信息在 Kubernetes 集群内是有效的,集群之外是无效的

Service可以看作是一组提供相同服务的Pod对外的访问接口。借助Service,应用可以方便地实现服务发现和负载均衡。对于Service 的工作原理请参考https://time.geekbang.org/column/article/68636

当需要从集群外部访问k8s里的服务的时候,方式有四种:ClusterIP(默认)、NodePortLoadBalancerExternalName

下面我们介绍一下这几种方式的区别

一、ClusterIP

该方式是指通过集群的内部 IP 暴露服务,但此服务只能够在集群内部可以访问,这种方式也是默认的 ServiceType。

我们先看一下最简单的Service定义

apiVersion: v1
kind: Service
metadata:
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376

这里我使用了 selector 字段来声明这个 Service 只携带了 app=hostnames 标签的 Pod。并且这个 Service 的 80 端口,代理的是 Pod 的 9376 端口。

我们定义一个 Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hostnames
spec:
  selector:
    matchLabels:
      app: hostnames
  replicas: 3
  template:
    metadata:
      labels:
        app: hostnames
    spec:
      containers:
      - name: hostnames
        image: k8s.gcr.io/serve_hostname
        ports:
        - containerPort: 9376
          protocol: TCP

这里我们使用的 webservice 镜像为 k8s.gcr.io/serve_hostname,其主要提供输出当前服务器的 hostname 的功能,这里声明的Pod份数是 3 份,此时如果我们依次访问 curl 10.0.1.175:80 的话,会发现每次响应内容不一样,说明后端请求了不同的 pod 。这是因为 Service 提供的负载均衡方式是 Round Robin

这里的 10.0.1.175 是当前集群的IP,俗称为 VIP,是 Kubernetes 自动为 Service 分配的。对于这种方式称为 ClusterIP 模式的 Service

Continue reading

mac下利用minikube安装Kubernetes环境

本机为mac环境,安装有brew工具,所以为了方便这里直接使用brew来安装minikube工具。同时本机已经安装过VirtualBox虚拟机软件。

minikube是一款专门用来创建k8s 集群的工具。

一、安装minikube

参考 https://kubernetes.io/docs/tasks/tools/install-minikube/, 在安装minkube之前建议先了解一下minikube需要的环境https://kubernetes.io/docs/setup/learning-environment/minikube/

1. 先安装一个虚拟化管理系统,如果还未安装,则在 HyperKit、VirtualBox 或 VMware Fusion 三个中任选一个即可,这里我选择了VirtualBox。

如果你想使用hyperkit的话,可以直接执行 brew install hyperkit 即可。

对于支持的driver_name有效值参考https://kubernetes.io/docs/setup/learning-environment/minikube/#specifying-the-vm-driver, 目前docker尚处于实现阶段。

$ brew install minikube

查看版本号

$ minikube version

minikube version: v1.8.2
commit: eb13446e786c9ef70cb0a9f85a633194e62396a1

安装kubectl命令行工具

$ brew install kubectl

二、启动minikube 创建集群

$ minikube start --driver=virtualbox

如果国内的用户安装时提示失败”VM is unable to access k8s.gcr.io, you may need to configure a proxy or set –image-repository”,
则指定参数–image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers

 $ minikube start --driver=virtualbox --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers

😄 minikube v1.8.2 on Darwin 10.15.3
✨ Using the virtualbox driver based on existing profile
✅ Using image repository registry.cn-hangzhou.aliyuncs.com/google_containers
💾 Downloading preloaded images tarball for k8s v1.17.3 …
⌛ Reconfiguring existing host …
🏃 Using the running virtualbox “minikube” VM …
🐳 Preparing Kubernetes v1.17.3 on Docker 19.03.6 …
> kubelet.sha256: 65 B / 65 B [————————–] 100.00% ? p/s 0s
> kubeadm.sha256: 65 B / 65 B [————————–] 100.00% ? p/s 0s
> kubectl.sha256: 65 B / 65 B [————————–] 100.00% ? p/s 0s
> kubeadm: 37.52 MiB / 37.52 MiB [—————] 100.00% 1.01 MiB p/s 37s
> kubelet: 106.42 MiB / 106.42 MiB [————-] 100.00% 2.65 MiB p/s 40s
> kubectl: 41.48 MiB / 41.48 MiB [—————] 100.00% 1.06 MiB p/s 40s
🚀 Launching Kubernetes …
🌟 Enabling addons: default-storageclass, storage-provisioner
🏄 Done! kubectl is now configured to use “minikube”

另外在minikube start 有一个选项是–image-mirror-country=’cn’  这个选项是专门为中国准备的,还有参数–iso-url,官方文档中已经提供了阿里云的地址……… 这个选项会让你使用阿里云的镜像仓库,我这里直接指定了镜像地址。

对于大部分国内无法访问到的镜像k8s.gcr.io域名下的镜像都可以在http://registry.cn-hangzhou.aliyuncs.com/google_containers 找到。

Continue reading

Golang中的限速器 time/rate

在高并发的系统中,限流已作为必不可少的功能,而常见的限流算法有:计数器、滑动窗口、令牌桶、漏斗(漏桶)。其中滑动窗口算法、令牌桶和漏斗算法应用最为广泛。

常见限流算法

这里不再对计数器算法和滑动窗口作介绍了,有兴趣的同学可以参考其它相关文章。

漏斗算法

非常很好理解,就像有一个漏斗容器一样,漏斗上面一直往容器里倒水(请求),漏斗下方以固定速率一直流出(消费)。如果漏斗容器满的情况下,再倒入的水就会溢出,此时表示新的请求将被丢弃。可以看到这种算法在应对大的突发流量时,会造成部分请求弃用丢失。

可以看出漏斗算法能强行限制数据的传输速率。

漏斗算法

令牌桶算法

从某种意义上来说,令牌算法是对漏斗算法的一种改进。对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发情况。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。

令牌桶算法是指一个固定大小的桶,可以存放的令牌的最大个数也是固定的。此算法以一种固定速率不断的往桶中存放令牌,而每次请求调用前必须先从桶中获取令牌才可以。否则进行拒绝或等待,直到获取到有效令牌为止。如果桶内的令牌数量已达到桶的最大允许上限的话,则丢弃令牌。

Golang中的限制算法

Golang标准库中的限制算法是基于令牌桶算法(Token Bucket) 实现的,库名为golang.org/x/time/rate

Continue reading

Golang中的两个定时器 ticker 和 timer

Golang中time包有两个定时器,分别为 tickertimer。两者都可以实现定时功能,但各自都有自己的使用场景。

Ticker定时器

package main

import (
	"fmt"
	"time"
)

func main() {
    // Ticker 包含一个通道字段C,每隔时间段 d 就向该通道发送当时系统时间。
    // 它会调整时间间隔或者丢弃 tick 信息以适应反应慢的接收者。
    // 如果d <= 0会触发panic。关闭该 Ticker 可以释放相关资源。

	ticker1 := time.NewTicker(5 * time.Second)
	// 一定要调用Stop(),回收资源
	defer ticker1.Stop()
	go func(t *time.Ticker) {
		for {
			// 每5秒中从chan t.C 中读取一次
			<-t.C
			fmt.Println("Ticker:", time.Now().Format("2006-01-02 15:04:05"))
		}
	}(ticker1)

	time.Sleep(30 * time.Second)
	fmt.Println("ok")
}

执行结果

开始时间: 2020-03-19 17:49:41
Ticker: 2020-03-19 17:49:46
Ticker: 2020-03-19 17:49:51
Ticker: 2020-03-19 17:49:56
Ticker: 2020-03-19 17:50:01
Ticker: 2020-03-19 17:50:06
结束时间: 2020-03-19 17:50:11
ok

可以看到每次执行的时间间隔都是一样的。

Timer定时器

package main

import (
	"fmt"
	"time"
)

func main() {

	// NewTimer 创建一个 Timer,它会在最少过去时间段 d 后到期,向其自身的 C 字段发送当时的时间
	timer1 := time.NewTimer(5 * time.Second)

	fmt.Println("开始时间:", time.Now().Format("2006-01-02 15:04:05"))
	go func(t *time.Timer) {
		times := 0
		for {
			<-t.C
			fmt.Println("timer", time.Now().Format("2006-01-02 15:04:05"))

			// 从t.C中获取数据,此时time.Timer定时器结束。如果想再次调用定时器,只能通过调用 Reset() 函数来执行
			// Reset 使 t 重新开始计时,(本方法返回后再)等待时间段 d 过去后到期。
			// 如果调用时 t 还在等待中会返回真;如果 t已经到期或者被停止了会返回假。
			times++
			// 调用 reset 重发数据到chan C
			fmt.Println("调用 reset 重新设置一次timer定时器,并将时间修改为2秒")
			t.Reset(2 * time.Second)
			if times > 3 {
				fmt.Println("调用 stop 停止定时器")
				t.Stop()
			}
		}
	}(timer1)

	time.Sleep(30 * time.Second)
	fmt.Println("结束时间:", time.Now().Format("2006-01-02 15:04:05"))
	fmt.Println("ok")
}

执行结果

开始时间: 2020-03-19 17:41:59
timer 2020-03-19 17:42:04
调用 reset 重新设置一次timer定时器,并将时间修改为2秒
timer 2020-03-19 17:42:06
调用 reset 重新设置一次timer定时器,并将时间修改为2秒
timer 2020-03-19 17:42:08
调用 reset 重新设置一次timer定时器,并将时间修改为2秒
timer 2020-03-19 17:42:10
调用 reset 重新设置一次timer定时器,并将时间修改为2秒
调用 stop 停止定时器
结束时间: 2020-03-19 17:42:29
ok

可以看到,第一次执行时间为5秒以后。然后通过调用 time.Reset() 方法再次激活定时器,定时时间为2秒,最后通过调用 time.Stop() 把前面的定时器取消掉。

timer和ticker的区别

  • ticker定时器表示每隔一段时间就执行一次,一般可执行多次。
  • timer定时器表示在一段时间后执行,默认情况下只执行一次,如果想再次执行的话,每次都需要调用 time.Reset() 方法,此时效果类似ticker定时器。同时也可以调用 Stop() 方法取消定时器
  • timer定时器比ticker定时器多一个 Reset() 方法,两者都有 Stop() 方法,表示停止定时器,底层都调用了stopTimer() 函数。

注意事项

1. 这里需要注意的时,如果在调用 time.Reset() time.Stop() 的时候,timer已经过期或者停止了,则会返回false

func main() {
	// timer 过期
	timer := time.NewTimer(2 * time.Second)
	time.Sleep(3 * time.Second)
	ret := timer.Reset(2 * time.Second)
	fmt.Println(ret)

	// timer 停止
	timer = time.NewTimer(2 * time.Second)
	timer.Stop()
	ret = timer.Reset(2 * time.Second)
	fmt.Println(ret)

	fmt.Println("ok")
}

执行结果

false
false
ok

2. 如果调用 time.Stop() 时,timer已过期或已stop,则并不会关闭通道。

3. 使用 time.NewTicker() 定时器时,需要使用 Stop() 方法进行资源释放,否则会产生内存泄漏,(Stop the ticker to release associated resources.)

认识虚拟内存

什么是虚拟内存

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。

为什么需要虚拟内存

我们知道程序执行指令的时候,程序计数器是顺序地一条一条指令执行下去,这一条条指令就需要连续地存储在一起,所以就需要这块内存是连续的。物理内存是有限的,如果多个程序同时运行的话,访问同一个物理地址的话,就有可能内存地址冲突,怎么办呢?

这时就需要虚拟内存发挥的作用了,程序里有指令和各种内存地址,系统从物理内存申请一段地址,与这个程序指令里用到的内存地址建立映射关系,这样实际程序指令执行的时候,会通过虚拟内存地址,找到对应的物理内存地址执行。对于任何一个程序来说,它看到的都是同样的内存地址。我们只需要维护一个虚拟内存到物理内存的映射表即可。

这种从物理内存申请一段地址建立映射的方法,我们称其为内存分段

Continue reading