Spanner Distributed Database 阅读

Introduction Spanner 数据库中的数据是分片(Shard)分散存储在多个数据中心的,数据是用 Paxos 算法(状态机)保证一致性。如果数据中心发生变化,Spanner 自动做 reshard。 Spanner 中的数据是存储在 schematized semi-relational table 上的。 在 commit 的时候把 timestamp 作为数据的 version。老版本的数据可以被垃圾回收,client 也可以读老数据。 Spanner 有两个特性是一般的分布式系统非常难实现的, 读和写操作的外部一致性。 在一个时间戳下,读操作是全局一致的。 能实现以上两点,是因为 Spanner 可以分配全球范围内保持一致的 commit timestamp。Spanner 的 timestamp 靠 TrueTime API 实现,甚至可以用 GPS 和原子钟来提高 TrueTime API 的精度。 Implementation 一个 Spanner 集群被称为一个 universe,如下图所示 Spanner 被组织成许多个 zone 的集合,zone 是管理部署的基本单元。一个数据中心可能会有多个 zone,zone 也是物理隔离的单元,例如,两个不同应用的数据就会被分散在两个 zone 上。 Zonemaster 把数据分配给 spanserver,spanserver 是真正存数据的地方 Client 从 location proxy 定位数据在哪个 spanserver 上 Universe master 主要是一个管理界面 Placement driver 周期性的与 spanserver 交互,进行负载均衡 Spanserver Software Stack 每一台 spanserver 上会存 100 到 1000 张表,每一张表上都有一个 Paxos 状态机,每一个 Paxos 状态机都会把自己的 metadata 和 log 存在 tablet 上。 ...

2020-10-03 · Me

Kubernetes 的 Volume 和 StorageClass

Kubernetes 的 Pod 可以 mount 很多种 Volume,常见的 volume 有 emptyDir hostPath configMap, secret persistentVolumeClaim nfs, gitRepo, cephfs, iscsi, cinder 等 其中,emptyDir 是最简单的一种,用于挂载一些临时文件,比如同一个 Pod 中两个 container 需要通过 unix socket 通信,那么把 socket 放在 emptyDir 中是最简单的方法。 甚至可以指定把这个抽象的目录放在内存,从而加快速度。 volumes: - name: html emptyDir: medium: Memory hostPath 是把数据直接存在 kubernetes 某个 worker node 上,这种方法一般不推荐使用,因为当 Pod 被调度到其他节点上后,数据就丢失了。 那么什么样的情况适合挂载 hostPath 呢?一些系统级的组件,需要挂载 node 上系统本身自带的一些文件时,比如需要读取 host 的 cert 目录,或者 etc 目录。常见的有 kube-system 空间下的 coreDNS 组件等。 当 Pod 中的程序需要把数据持久化到外部存储时,最推荐的用法是先在系统中定义 StorageClass,然后配合 persistentVolumeClaim (PVC) 和 persistentVolume (PV) 一起动态的分配空间。 ...

2020-09-28 · Me

Kubernetes Headless Service

问题起源于我用 envoy 对 grpc 做 Layer 7 负载均衡的时候,发现 traffic 永远被转发到了一个特定的 Pod,显然是配置出错了。 环境如下: 同一个 namespace 下部署了 2 个 grpc server,一个 envoy $ kubectl get pod NAME READY STATUS RESTARTS AGE grpc-envoy-7684f49cb-9fv4h 1/1 Running 0 4h49m grpc-server-668bdd6576-2bvkz 1/1 Running 0 4h51m grpc-server-668bdd6576-tqzj4 1/1 Running 0 4h51m envoy 的 service 配置如下 apiVersion: v1 kind: Service metadata: name: grpc-envoy namespace: default labels: app: grpc-envoy spec: type: NodePort ports: - name: grpc port: 8080 targetPort: grpc nodePort: 30061 protocol: TCP selector: app: grpc-envoy grpc-server 的 service 配置如下 ...

2020-09-20 · Me

Kubernetes Pod 中的 Pause 容器

在一个运行 kubernetes 的节点上,我们能看到很多名叫 “pause” 的 container。比如 $ sudo docker ps | grep pause a4218d1d379b k8s.gcr.io/pause:3.1 "/pause" a2109bf3f0db k8s.gcr.io/pause:3.1 "/pause" 57cfa42e95d3 k8s.gcr.io/pause:3.1 "/pause" 仔细观察一下不难发现,每一个 Pod 都会对应一个 pause container。 在查阅了网上的一些资料以后,我总结了一下它大概有两个作用, 它是 Pod 中第一个启动的 container ,由它创建新的 linux namespace,其他 container 启动后再加入到这些 namespace 中。 在 Pod 的环境中充当 init process 的角色,它的 PID 是 1,负责回收所有僵尸进程。 说个题外话,在 docker 中,一个 container 启动时,Dockerfile 的 ENTRYPOINT 中指定的命令会成为这个 container 的 init process,PID 为 1. 顺便来看一下 pause 容器的实现,一共只有几十行 C 语言代码 static void sigdown(int signo) { psignal(signo, "Shutting down, got signal"); exit(0); } static void sigreap(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0) ; } int main(int argc, char **argv) { if (getpid() != 1) /* Not an error because pause sees use outside of infra containers. */ fprintf(stderr, "Warning: pause should be the first process\n"); if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) return 1; if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) return 2; if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, .sa_flags = SA_NOCLDSTOP}, NULL) < 0) return 3; for (;;) pause(); fprintf(stderr, "Error: infinite loop terminated\n"); return 42; } 函数 sigreap(int signo) 就是在子进程退出的时候进行回收,防止产生僵尸进程,所以是 “reap”,非常形象生动。 ...

2020-09-13 · Me

Unicode 字符编码

ASCII 我们熟悉的 ASCII 码可以说是字符编码的始祖了。它规定了常用的数字、符号、英文字母与二进制之间的对应关系。 ASCII 的缺点是字符集太少了,只能表示英文和数字,无法表示像中文,日文这样的符号。因此人们就设计出了 Unicode 字符集,囊括了几乎所有人类语言文字的符号。 Unicode Unicode 是一个字符集,而不是一种编码方式。 Unicode 相当于是给人类所有的符号一个独一无二的 ID,只要大家都是用这个 ID 表示字符,就不会出现乱码的问题。 因为 Unicode 是一个字符集,因此它不存在所谓的 “用几个字节表示 unicode” 这样的问题,这是具体的编码方式需要处理的事。 Unicode 把 ID 划分成了 17 组 (Plane),每组有 65536 个字符,编号可以用 U+[XX]YYYY 这样的形式表示,每一位是一个十六进制数字,其中 XX 代表组编号,从 0 到 0x10,一共17个,YYYY 代表这一组中的字符编号,一共 65536 个。 其中第 0 组叫 Basic Multilingual Plane,简称 BMP,它是 Unicode 中最基础和最常用的一部分,码点范围是U+0000 ~ U+FFFF,包含了我们常用的英文和汉字。 UFT-8 UTF-8 是 Unicode 具体的编码方式,除此之外还要 UTF-16, UTF-32 等等。 为什么需要编码方式呢? 直接用 Unicode 的 ID 不就行了吗? 因为我们需要节省存储空间。 UTF-8 是一种变长的编码方式,它可以使用 1-4 个字节表示一个符号,编码规则如下 ...

2020-08-16 · Me

Kubernetes Dashboard 添加 Auth

组里的 k8s cluster Dashboard 一直没有设置登录,导致所有可以 ping IP 的人都可以登录管理界面,这样显然是不合理的,应该要设置几个不同权限的账户,并且开启 Dashboard 的 Basic Auth。 关于开启 Dashboard Basic Auth 网上有不少资料,但是在实际操作中还是遇到几个莫名其妙的坑。 创建用户文件 根据实际需求,并不需要使用 LDAP 等等复杂的登录方式,我们只需要一个 admin 账户,再加上低权限的只读 view 账户,以及有修改权限的 edit 账户就足够。 因此,只要添加 /etc/kubernetes/pki/basic_auth_file 文件即可。 vi /etc/kubernetes/pki/basic_auth_file password,username,1 需要注意的一个坑是用户名密码的顺序是反过来的(如上面所示),否则在 dashboard 上怎么输入都提示不对。 修改 API Server 配置 修改 /etc/kubernetes/manifests/kube-apiserver.yaml 加入一个启动参数 vim /etc/kubernetes/manifests/kube-apiserver.yaml - --basic-auth-file=/etc/kubernetes/pki/basic_auth_file 这里又遇到一个坑,在我们的 k8s Master 节点这个目录下有两个文件,第一个叫 kube-apiserver.yaml, 另一个是 kube-apiserver_xxx.yaml。 本来我以为第一个是实际的配置文件,第二个应该是其他人配置时 copy 的一个备份。 然而实际并不是,真正被使用的是第二个文件,这点让人匪夷所思,我花了好久才发现这个坑,但是我始终没找到哪里指定了让 api server 读取 kube-apiserver_xxx.yaml 而不是 kube-apiserver.yaml 。 重启 API Server 很多资料上都会把重启 api server 一笔带过,但是都不写具体怎么操作,k8s 上并不是简单删除一个 pod 就算重启的。 ...

2020-08-04 · Me

在虚拟机中使用 GPU 计算

本文介绍如何在 Linux 虚拟机中直接使用 GPU 做科学计算,要达到这个目的,需要满足下面几个条件: 物理主机使用 VMWare ESXi 作为虚拟化的 VMM,并且版本最好大于等于 6.5 使用的是 Nvidia GPU 的显卡 Linux 虚拟机 OS 没有限制,我使用的是 ubuntu ESXi 开启显卡直通 假设已经安装好了 ESXi,通过 WebUI 进入 Host 的 Manage 界面,点击 Hardware,如图 把 nVidia 开头的这几个全部选中,然后 “Active”, 表示开启 PCI 设备的直通 (passthrough)。 重启物理主机。 配置虚拟机 创建一个新虚拟机,或者修改已有的虚拟机, 点击 Edit,VM Options ,在 Advanced 里面点击 Edit configuration 。 增加一条配置参数 hypervisor.cpuid.v0, 对应的值为 FALSE,这一步的目的是让驱动把虚拟机当做物理机来处理。 另一需要修改的地方让虚拟机硬件配置内存大小下面勾选 “Reserve all guest memory (All locked)”,让虚拟机启动时一次性获取物理主机内存,而不是按需获取。 到这里,主机和虚拟机的配置就全部完成了,接下来是驱动软件的安装 虚拟机安装驱动 重启并进入虚拟机 CLI,首先可以确认一下 GPU 已经被直通给了虚拟机,这一步不是必须要做,但检查一下没坏处。 ...

2020-06-13 · Me

Kubeflow 部署 MNIST

在阅读本文之前,假设已经在 GCP 上安装好了 Kubeflow。 首先进入 Kubeflow,点击 Notebook Server,新建一个 Jupyter Notebook。 新建的时候会让你输入 Name 和 Namespace,在 Kubeflow 中,每个用户都在 k8s 集群上有自己的 Namespace。 这里输入的 Name 对应的 Notebook Pod 最后会在自己的 Namespace 下。 新的 Notebook 里面是空的,我们需要下载一些例子。打开 terminal 然后输入 git clone 命令: git clone https://github.com/kubeflow/examples.git 回到默认界面会看到刚刚 clone 的项目,打开 mnist 目录下的 mnist_gcp.ipynb 开始 首先第一个问题,当我打开这个 Jupyter Notebook 的 WebUI 时,它运行在哪里? Notebook 是在哪个 Pod $ kubectl -n Your-namespace get pod NAME READY STATUS RESTARTS AGE fairing-builder-chvkq-6s4cn 0/1 Completed 0 3d23h mnist-model-7886dcbb5b-t2kk8 1/1 Running 0 3d22h mnist-tensorboard-774c585b7c-65766 2/2 Running 0 21h mnist-train-2596-chief-0 0/1 Completed 0 3d22h mnist-train-2596-worker-0 0/1 Completed 0 3d22h mnist-ui-7f95c8498b-xqsfs 2/2 Running 0 3d22h test1-0 2/2 Running 0 3d23h test1-0 是之前在 UI里面创建 Notebook server 时定下的名字,于是test1-0 ...

2020-03-22 · Me

[Istio] 使用 istio 控制转发流量

概念 首先介绍几个概念,Ingress 指的是进入到 k8s 集群中的 traffic,比如一个 client 发起的 HTTP 请求,经过层层网络最终到达了 k8s cluster 的外部,那么让不让它进入到 cluster 内部就是 ingress controller 做的事。 Kubernetes 原生提供了自己的 Ingress Controller,此外还有很多第三方的 Ingress Controller,istio 就是其中之一。需要注意的是,本文所有部署都是基于 GKE,其他的云平台可能略有不同。 在 k8s 中安装了 istio 之后,就可以用 istio 来控制所有进入 cluster 的流量。如何安装 istio 不在本文的范围,读者可以参考 istio 官方文档。 安装 istio 完成之后,kubectl get namespace 命令可以看到有个名叫 istio-system 的空间,所有 istio 组件的 pod 都在这个空间中。 然后用如下命令查看 istio ingressgateway $ kubectl -n istio-system get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT istio-ingressgateway LoadBalancer 10.0.23.180 35.222.xxx.xxx 15020:32011/TCP,80:30444/TCP 我们会看到这个名叫 istio-ingressgateway 的 LoadBalancer 它有公网 IP 地址 35.222.xxx.xxx,也就是说任何人都可以 ping 这个地址。 ...

2020-03-15 · Me

用 Grafana 展示监控状态

运维或者 SRE 部门经常会弄一个大屏幕展示各种系统状态,看上去很好玩,于是我也用类似的开源软件监控一下家里的主机。 整个过程非常简单,主要是安装三个软件 Node exporter,Prometheus,Grafana。 Node exporter 既然要展示系统状态,那么第一步就是要获得系统的状态数据,比如 CPU 使用率,内存使用率,网络流量等。 Prometheus 官方提供了一个使用 go 语言编写的程序 node_exporter,直接下载项目主页上 release 里的二进制即可。node_exporter 最好直接安装在物理主机上,因为这样才能采集到最准确的数据。 运行 node_exporter 以后,会自动启动一个 http server 并且监听 9100 端口,如果有 client 过来访问, server 返回主机的监控信息。比如: $ curl http://localhost:9100/metrics node_network_transmit_packets_total{device="veth126cb08"} 28859 node_network_transmit_packets_total{device="veth1276a16"} 1383 node_network_transmit_packets_total{device="veth749c501"} 1.108492e+06 返回信息的格式是符合 Prometheus 定义的标准的,因此 Prometheus 能够处理并以简单的图标的形式展现这些数据。 看到这里大家应该不难想到,如果我自己写一个程序 HelloWorld,并且把程序的状态按照一定的格式导出,那么同样可以通过 Prometheus + Grafana 展现。 Prometheus Prometheus 是一个功能齐全的数据库,还提供了 PromSQL 语言方便用户查询,以及一个简单的网页前端。 最简单快捷的方式当然是启动一个容器,唯一需要注意的是把配置文件 prometheus.yml 挂载到容器的 /etc/prometheus/ 目录下。 $ docker run -d -p 9090:9090 \ -v /home/prometheus/:/etc/prometheus/ prom/prometheus 配置文件中需要在 scrape_configs 部分添加 noder exporter 的 IP 地址和端口。 ...

2020-02-02 · Me