Kubernetes 部署 Tensorflow Serving

本文记录如何把 TensorFlow ResNet 模型部署在本地 Kubernetes 集群上,并提供一个 grpc 端口供集群外部访问。 本文不牵涉 ResNet(Deep residual networks)模型的实现细节,只讨论部署。 本文来源于 TensorFlow 官网上的一个例子,但正如大多数项目的文档一样,文档落后于项目的发展,因此有一些小坑,这里记录一下。 下载 ResNet 模型数据 这一步没什么好说的,按照步骤下载就行了 mkdir /tmp/resnet curl -s http://download.tensorflow.org/models/official/20181001_resnet/savedmodels/resnet_v2_fp32_savedmodel_NHWC_jpg.tar.gz | \ tar --strip-components=2 -C /tmp/resnet -xvz 制作并启动 ResNet serving 因为我们要把这个 serving 部署到 k8s,所以制作 docker 镜像是必须的。 先启动运行一个空 serving 镜像: docker run -d --name serving_base tensorflow/serving 然后把刚刚下载的 /tmp/resnet 文件夹下的所有内容拷贝到容器中: docker cp /tmp/resnet serving_base:/models/resnet 最后,commit 生成一个自己的 image docker commit --change "ENV MODEL_NAME resnet" serving_base resnet_serving docker kill serving_base docker rm serving_base 然后我们试着运行一下这个镜像,要是看到类似如下输出,证明启动正常。 ...

2020-01-20 · Me

Kubernetes DNS

在介绍 Kubernetes 中的 DNS 之前,我们先来看看 Kubernetes 中的另一个概念 Service,以及为什么需要 Service。 什么是 Service 我们知道 k8s 集群中应用的部署是以 Pod 为单位的,在 Pod 内执行 ifconfig 可以看到每个 Pod 都有自己的 IP,这个 IP 在集群内部是唯一的,其他 Pod 都能 ping 这个地址。 这样的设计使得 Pod 里的应用程序可以直接交互。 另一方面,Pod 的生命周期是短暂的,因此 Pod IP 也是不断变化的,而且 Pod 也会有多个副本。 那么问题来了: 其他程序访问这个 Pod 时该用哪个 IP 地址呢 ? 这个时候就需要 Service 出场了, Service 对外只会提供一个 IP,一个请求到来时 Service 决定该转发到后面哪一个 Pod 上。 我们可以理解为 Service 加上的一组 Pod 可以看做是一个微服务。 Service 对外提供 ClusterIP, NodePort 等访问方式。 Kubernetes DNS 上面提到 Service 对外提供一个唯一的 IP,但这个 IP 偶尔也会随着 service 的更新改变,所以还需要一个 k8s 集群内部的 DNS 把服务对应到 IP 上。 ...

2020-01-19 · Me

安装 Kubernetes Dashboard

Kubernetes Dashboard 是一个 Web UI 的集群管理工具。项目主页在这里 首先根据它的主页上 README 里面的内容直接 kubectl apply -f recommended.yaml,这样集群中就会创建并运行 dashboard 的 POD。 接下来的问题是如何从外界访问到这个 UI。 我的 k8s 集群环境是一台物理主机上的三台虚拟机,每个虚拟机都是 Headless 启动,也就是说纯命令行没有桌面环境,无法打开浏览器,因此项目主页上说的 kubectl proxy 访问 http://localhost:8001 的方式不适用。 我希望最终能从物理主机上访问到这个 WebUI。 使用 NodePort 我们查看一下 kubernetes-dashboard 使用 recommended.yaml 部署之后 service 的类型是 ClusterIP $ kubectl get svc --all-namespaces NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 17d default redis-nodeport NodePort 10.96.39.42 <none> 6379:31250/TCP 15d kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 17d kubernetes-dashboard dashboard-metrics-scraper ClusterIP 10.96.160.248 <none> 8000/TCP 6m44s kubernetes-dashboard kubernetes-dashboard ClusterIP 10.96.132.59 <none> 443/TCP 6m45s ClusterIP 是在 k8s 集群内部可以访问的 IP,在集群之外(例如我的物理主机)是无法 ping 通的,因此改成 NodePort 类型。 ...

2020-01-18 · Me

本地搭建三节点 Kubenetes

赶在 2019 年快结束之际,写一篇博客作为本年度的收官之作吧。 前言 虽然现在各大云平台厂商都提供了一键搭建 kubenetes 的服务,但缺点是费用太贵了,如果仅仅把它作为自己没事折腾的小玩具非常不划算,另外虽然也可以用公司的账号,但并不想把自己的折腾的东西跟工作混为一谈。 所以决定自己在主机上手动搭建一个。 准备工作 一般 kubenetes 至少要三台 linux 主机组建成一个 cluster,因为手头没有三台 linux 物理主机,所以要用虚拟机代替。 kubenetes 各个虚拟机节点的规划如下: 主机名 主机 IP OS 集群角色 192-168-56-10.master 192.168.56.10 Ubuntu 18.04 master 192-168-56-11.node 192.168.56.11 Ubuntu 18.04 node1 192-168-56-12.node 192.168.56.12 Ubuntu 18.04 node2 准备工作主要为下面几步: 物理主机内存 16G,操作系统为 Ubuntu 18.04 安装 Virtualbox 6.1 创建三台虚拟机,每台内存 4G 对三台虚拟机做基本配置 VirtualBox 虚拟机配置 默认只有一个 NAT 适配器,我们需要添加一个 Host-Only Adapter。NAT 适配器是虚拟机用来访问互联网的,Host-Only 适配器是用来虚拟机之间通信的。上面表格所指的 主机 IP 也是这个 Host only IP。 vbox 上配置一个 host only adapter, 默认名字 vboxnet0,网络地址 192.168.56.1/24 ...

2019-12-28 · Me

CPU affinity

CPU affinity – CPU 亲和性,指进程更希望运行在哪个 CPU core 上。 指定 core 有什么好处呢? 比如,可以自己决定哪些程序可以独占 CPU 资源,保证这个程序性能的最大化; 指定 CPU 以后可以提高 Cache 的命中率,常用于一些对性能非常高要求的程序,例如 nginx。 命令行指令 taskset 在 Linux 系统中,我们可以用 taskset 命令指定一个进程运行在哪个核心上。 比如我们写一个程序用 while(1) 制造死循环,那么运行这个程序的时候 CPU 会飙到 100% 用以下这条命令运行这个程序 taskset -c 3 ./a.out 意思是把 a.out 运行在从 0 开始数起的第 3 个核心上。 于是,用 htop 命令查看,会看到第 4 个核 CPU 使用率是 100%。 编程的 API 那么在程序的代码里怎么用呢? 先来看看 glibc 提供的系统 API #include <sched.h> int sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask); int sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask); void CPU_CLR(int cpu, cpu_set_t *set); int CPU_ISSET(int cpu, cpu_set_t *set); void CPU_SET(int cpu, cpu_set_t *set); void CPU_ZERO(cpu_set_t *set); nginx 的 config 文件中,可以为每个工作进程绑定CPU ...

2019-09-15 · Me

从 Kubernetes 中访问 Memorystore

Memorystore 是 Google Cloud 在 2018 年推出的托管 Redis 服务,让用户一键生成 Redis 实例,必要的时候再一键 scale,省去了维护 Redis 的烦恼。 本文在 k8s 中部署一个简单的小程序访问 Memorystore 数据库,获取 counter 值,并开启一个 http server 对外提供这个值。 准备 GCP 提供了一个命令行工具 gcloud,几乎所有的 web 操作都有对应的 CLI,非常方便。不同操作系统对应的安装包可以在这里下载 我的笔记本就叫它 “local host”,安装好 gcloud 之后,以下所有的操作都在 local 进行,命令执行的结果直接部署到 cloud 中。 现在开始前期准备工作。首先,在 GCP web 界面一键创建 Memorystore,之后我们能在 MemoryStore 的 Instances 里面看到这个实例,它的 IP 地址是 10.0.16.3 端口 6379。 很显然,10.0.16.3 这个 IP 是无法直接访问的,而如果你在相同的 GCP Project 里面创建了一个 VM instance,GCP 会自动创建一条路由,让你的 VM 可以 telnet 10.0.16.3 6379。 然后,创建一个 k8s 集群,这一步也同样可以在 web 界面里做,如果要用 GCP 提供的 gcloud 命令行的话如下: ...

2019-08-25 · Me

Docker 中编译 vim8.0

vim 的最新版 vim8.0 提供了很多新的特性,而且一些流行的 vim 插件很多功能也依赖于 8.0 版本,如果你要使用 vim8.0,那么最好的办法当然是使用操作系提供的软件包管理器一键安装,省时省力。 但是总有那么一些蛋疼的情况 ——你需要自己编译 vim。 本文就是记录下具体的步骤,并且把编译源码时需要安装的依赖软件全部做成 docker 镜像。 事情起因 某台服务器上我要用 vim8.0 的新特性,但是在服务器上我没有任何超级权限,只能读写我自己的 home 目录。 所以没法直接安装vim,只能从源码编译。 制作编译 vim 的docker image 编译 vim 需要系统中安装很多依赖软件,比如 vim 最基本的要包括 python2.7,luajit 等。 都 2019 年了,最好的方式当然是制作一个 docker 镜像,具体的步骤就不一一解释,贴上 Dockerfile 以示诚意。 FROM ubuntu:18.04 RUN apt-get update && apt-get install -y \ liblua5.1-dev \ luajit \ libluajit-5.1 \ python-dev \ ruby-dev \ libperl-dev \ libncurses5-dev \ libatk1.0-dev \ libx11-dev \ libxpm-dev \ libxt-dev \ gnupg2 \ curl \ && gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB \ && curl -sSL https://get.rvm.io | bash -s stable \ && /bin/bash -l -c "rvm install 2.6 && rvm use 2.6.3 --default" COPY ./configure.sh /root/ ENTRYPOINT ["/bin/bash"] 其中 configure.sh 是编译 vim 时你输入的一些 configure 选项,其实这个文件可有可无,没有的话手动输入就行了 ...

2019-08-16 · Me

How TCP backlog works in Linux

原文: http://veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html 当一个应用程序使用 listen() 系统调用把一个 socket fd 设置成 LISTEN 状态时,也需要指定一个 backlog 值。通常我们可以认为这个 backlog 代表这个 socket fd 可以接受最大的连接请求数。 #include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog); 因为 TCP 的三次握手,在 server 端 accept() 系统调用返回,并且tcp 状态在变成 ESTABLISHED 之前,会有一个短暂的 SYN RECEIVED 状态。那么这个状态的 tcp 链接应该放在哪个 queue 里面呢? 单个 queue,其大小就是 listen() 参数 backlog。当一个 SYN 包到达时,server 返回一个 SYN/ACK 给 client,并且把这个链接放入 queue。当 client 的 ACK 到达时,TCP 的状态变成 ESTABLISHED。这就意味着这一个 queue 有两种不同的状态:SYN RECEIVED 和 ESTABLISHED。只有在 ESTABLISHED 状态的链接才能被 accept() 返回给用户程序。 两个 queue,未完成的链接在 SYN queue,建立好的链接在 accept queue。也就是说 SYN REVEIVED 状态的链接会先被放到 SYN queue 里面,当它变成 ESTABLISHED 状态时再被移动到另一个 queue。所以,accept() 系统调用实现起来就简单了,它只从 accept queue 中获取链接返回给应用程序。这时,listen() 系统调用的 backlog 参数决定了 accept queue 的大小。 ...

2019-08-15 · Me

minikube, 单机版 kubernetes

本来想在我的 linux 主机上创建 3 个虚拟机,然后手工搭建一个拥有 3 个节点的 k8s 集群。 但是翻了翻网上的各种教程,发现每个教程都是巨复杂,给我一种 “即使我跟着教程千辛万苦敲完所有命令,也不一定能运行” 的感觉。最后,我发现了 minikube 这个东西,可以方便的搭建一个单机版 k8s。 麻雀虽小五脏俱全,即便是这样一个简单的 k8s,目前也足够我学习一些基本知识了。 本文记录一下安装 minikube 的具体步骤,并在 k8s 中部署一个简单的服务。 安装 minikube 开局一张图,先展示一下 minikube 的整个架构。 首先是准备工作,更新系统,安装必要组件。 sudo apt-get update sudo apt-get install apt-transport-https sudo apt-get upgrade 然后安装 virtualbox, sudo apt install virtualbox virtualbox-ext-pack 安装 minikube wget https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 chmod +x minikube-linux-amd64 sudo mv minikube-linux-amd64 /usr/local/bin/minikube 添加 kubectl 源 curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - 值得注意的是,我的主机是 ubuntu 18.04,代号 bionic,而安装的源却是 xenial,对应 ubuntu 16.04。 这是因为 kubernetes 源还没有为 18.04 更新,不过旧的源仍然可以用,亲测有效。 ...

2019-08-13 · Me

Golang 操作共享内存

前言 进程间通信的方式有很多种,如果两个进程分别在不同的机器上,那么使用 socket 通信;如果在同一台机器上,共享内存机制是一种快速高效的方式。 本文实现一个 go 语言二进制程序和 C 语言二进制程序通过共享内存交换数据。 提到共享内存主要有两种: System V 标准的 shmget/shmdt 等接口 POSIX 标准的 shm_open 等接口 另外 Linux 下 mmap() 匿名映射也是最常用的进程间共享内存方法。 创建了共享内存以后,一般会显示在系统的 /dev/shm 目录下。Linux 默认 /dev/shm 为实际物理内存的1/2, 比如我的机器上物理内存为 16G,运行 df 命令后可以看到 /dev/shm 的大小为 7.8G 。 $ df -h Filesystem Size Used Avail Use% Mounted on tmpfs 1.6G 3.2M 1.6G 1% /run tmpfs 7.8G 4.0K 7.8G 1% /dev/shm tmpfs, ramfs 和 ramdisk tmpfs是一个虚拟内存文件系统,在Linux内核中,虚拟内存资源由物理内存(RAM)和交换分区组成,Tmpfs可以使用物理内存,也可以使用交换分区。 ramdisk 是一个块设备,只不过它是存在于内存上的。 ramfs 也是文件系统,不过已经被 tmpfs 替代了。 ...

2019-08-02 · Me