为 Kubernetes 安装 DIY 裸机 GPU 集群
为 Kubernetes 安装 DIY 裸机 GPU 集群
原文:https://medium.com/hackernoon/installing-a-diy-bare-metal-gpu-cluster-for-kubernetes-364200254187
我不知道你是否见过来自 Canonical 的橙色盒子

这些机器真的很棒。它们包含 10 个英特尔 nuc,外加第 11 个用于管理层。它们被用作大型软件栈的演示工具,比如 OpenStack、Hadoop,当然还有 Kubernetes。
它们可以从 TranquilPC 免费获得,所以如果你是一个研发团队,或者只是想在家里拥有一个整洁的小集群,我鼓励你去看看。
然而,尽管他们拥有巨大的品质,但他们缺乏深度学习极客所珍视的关键工具:GPU!!
在这篇博客/教程中,我们将学习如何构建、安装和配置一个使用类似架构的 DIY GPU 集群。我们从硬件选择和实验开始,然后深入研究裸机管理系统 MAAS (金属即服务)。最后,我们看看如何在 Ubuntu 上部署 Kubernetes,添加 CUDA 支持,并在集群中启用它。
硬件:为英特尔 NUCs 添加成熟的 GPU?
当你看到它们时,很难说出如何将普通的 GPU 卡插入到英特尔 NUCs 的微小外形中。然而,他们有一个 m2 NGFF 港。这实际上是一个 PCI-e 4x 端口,只是外形不同。
而且,还有把 M.2 转换成 PCI-e 4x 的这个和把 PCI-e 4x 转换成 16x 的那个。
所以…理论上,我们可以将 GPU 连接到英特尔 nuc。让我们试试吧!!
POC:第一节点
让我们从简单的单节点英特尔 NUC 开始,看看我们是否能让 GPU 与之配合工作。
我已经有了一辆上一代的 NUC 和一辆老镭龙 7870,我不得不买了
- 为 GPU 供电的 PSU:为此,我发现 Corsair AX1500i 是市场上最划算的交易,能够为多达 10 个 GPU 供电!!如果我想用更多的节点来扩展它,这再好不过了。
- 适配器:
- 黑掉激活 PSU 上的电源,而无需将其连接到“真正的”主板上。感谢 Michael Iatrou (@iatrou)给我指明了方向。
- 很明显是屏幕、键盘、电缆…
把所有东西插在一起,然后…

它还活着!运行速度为 4x 的附加 GPU 的 Ubuntu 启动屏幕
此时,我们已经证明这是可能的,是时候开始构建一个真正的集群了。
添加小兄弟
让我们构建一个包含 2 个管理节点和 4 个 GPU 计算人员的 6 节点集群。
材料清单
对于每个工人,您需要:
- 英特尔 NUC6i5RYH 在性能方面是一个不错的选择,但它没有英特尔 AMT,因此您将无法激活完全自动化的操作。如果您想做一些更接近橙色盒子的事情,实现电源循环的完全自动化,您需要购买较旧的 NUC5i5MYHE,这是唯一一款采用博锐技术的参考产品(除了主板本身)。更多信息点击此处。
- RAM: 海盗船的 32GB DDR4】
- 固态硬盘: 500GB 闪迪 Ultra II
- 显卡: nVidia GTX1060 6GB
- 适配器:与之前相同,还有 4x 延长器
对于管理节点,是上述 nuc 的 2 倍,但没有 GPU,使用更小的 SSD。
现在是整体组件:
- PSU:海盗船 AX1500i
- 开关网件 GS108PE 。你可以拿一个低端开关,我有一个可用的就是这样。我在网络方面没有做任何稀奇古怪的事情。
- raspberry Pi:32GB micro SD 的任何 2 或 3 版本
- 间隔器
- ATX PSU 开关
仅供参考,这是一个昂贵的设置,但 AWS 的 g2 实例的投资回报率约为每个节点 20 天…
执行
如果盒子里放不下,就把盒子拿走。所以首先要把 NUC 的主板取出来。使用 3 毫米厚的 PVC 板和垫片,我们可以有一个漂亮的布局。

GPU view
在 PVC 的一侧,我们连接了 GPU,因此电源连接器在底部可见,PCI-e 端口只是稍微高出边缘。孔是 2.8 毫米,以便 M3 垫片通过,但你需要拧他们一点点,他们不会移动。

Intel NUC view
另一方面,我们为 SSD 和 NUC 钻了固定孔,以便 PCI-e riser 电缆在 GPU 的 PCI-e 端口前面对齐。你还需要在固态硬盘的金属支架上钻一点孔。
正如你在图片上看到的,我们把立管放在 PEC 和 NUC 之间,这样看起来更好
我们对每个节点重复操作 4 次。然后使用我们的 50 毫米 M3 六角,我们用 3 个螺丝将它们连接在每个“刀片”之间,将所有东西都预订到网络上,然后…tadaaaaaa!!

Close up view from the NUC side

From the GPU side
软件:集群的安装
赋予集群生命需要在软件方面做大量的工作。
我们将利用强大的管理工具,而不是手动流程。这将使我们能够在未来重新规划我们的集群。
管理金属本身的工具是 MAAS (金属即服务)。它是由 Canonical 开发的,用于管理裸机服务器群,并且已经驱动了 Ubuntu Orange Box。
然后为了进行部署,我们将使用 Canonical 的建模工具 Juju ,它有用于部署 Kubernetes 的规范发行版的包。
裸机配置:安装 MAAS
首先,我们需要在 Raspberry Pi 2 上安装 MAAS。
对于其余部分,我们将假设您已经准备好:
- 安装了 Ubuntu Server 16.04 的 Raspberry Pi 2 或 3
- 连接到与互联网相连的网络的板载以太网端口,并进行配置
- 额外的 USB 转以太网适配器,连接到我们的集群交换机
网络安装程序
默认的 Ubuntu 镜像不会自动安装 USB 适配器。首先,我们将查询 ifconfig 以查看除 eth0 接口之外的 eth1(或其他名称):
$ /sbin/ifconfig -a
eth0 Link encap:Ethernet HWaddr b8:27:eb:4e:48:c6
inet addr:192.168.1.138 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::ba27:ebff:fe4e:48c6/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:150573 errors:0 dropped:0 overruns:0 frame:0
TX packets:39702 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:217430968 (217.4 MB) TX bytes:3450423 (3.4 MB)eth1 Link encap:Ethernet HWaddr 00:0e:c6:c2:e6:82
BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
我们现在可以用编辑/etc/network/interfaces . d/et h1 . CFG
# hwaddress 00:0e:c6:c2:e6:82
auto eth1
iface eth1 inet static
address 192.168.23.1
netmask 255.255.255.0
然后用以下命令启动 eth1
$ sudo ifup eth1
现在我们有了第二个接口设置。
基础安装
让我们首先安装需求:
$ sudo apt update && sudo apt upgrade -yqq
$ sudo apt install -yqq — no-install-recommends \
maas \
bzr \
isc-dhcp-server \
wakeonlan \
amtterm \
wsmancli \
juju \
zram-config
让我们利用这个机会来修复非常烦人的 Perl Locales bug,它几乎影响了周围的每个 Rapsberry Pi:
$ sudo locale-gen en_US.UTF-8
现在,让我们激活 zram,通过在 /etc/rc.local 中添加以下内容,将我们的 ram 虚拟增加 1GB
modprobe zram && \
echo $((1024*1024*1024)) | tee /sys/block/zram0/disksize && \
mkswap /dev/zram0 && \
swapon -p 10 /dev/zram0 && \
exit 0
并通过以下方式立即激活
$ sudo modprobe zram && \
echo $((1024*1024*1024)) | sudo tee /sys/block/zram0/disksize && \
sudo mkswap /dev/zram0 && \
sudo swapon -p 10 /dev/zram0
DHCP 配置
DHCP 将由 MAAS 直接处理,所以我们不必处理它。然而,它配置默认设置的方式是相当残酷的,所以你可能想调整一下。下面是一个 /etc/dhcp/dhcpd.conf 文件,这个文件可以工作,但是有点花哨
authoritative;
ddns-update-style none;
log-facility local7;option subnet-mask 255.255.255.0;
option broadcast-address 192.168.23.255;
option routers 192.168.23.1;
option domain-name-servers 192.168.23.1;
option domain-name “maas”;
default-lease-time 600;
max-lease-time 7200;subnet 192.168.23.0 netmask 255.255.255.0 {
range 192.168.23.10 192.168.23.49;host node00 {
hardware ethernet B8:AE:ED:7A:B6:92;
fixed-address 192.168.23.10;
}... ...
}
我们还需要告诉 dhcpd 只为 eth1 上的请求提供服务,以防止流入我们的其他网络。我们通过编辑/etc/default/isc-DHCP-server中的接口选项来做到这一点
# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
# Separate multiple interfaces with spaces, e.g. “eth0 eth1”.
INTERFACES=”eth1"
最后,我们用
$ sudo systemctl restart isc-dhcp-server.service
简单的路由器配置
在我们的设置中,Raspberry Pi 是网络的争用点。虽然 MAAS 默认提供 DNS 和 DHCP,但它并不作为网关运行。因此,我们的节点很可能最终看不到互联网,这显然是我们不希望的。
首先,我们在 sysctl 中激活 IP 转发:
sudo touch /etc/sysctl.d/99-maas.conf
echo “net.ipv4.ip_forward=1” | sudo tee /etc/sysctl.d/99-maas.conf
sudo sh -c “echo 1 > /proc/sys/net/ipv4/ip_forward”
然后,我们需要链接 eth0 和 eth1 接口,以允许它们之间的流量
$ sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
$ sudo iptables -A FORWARD -i eth0 -o eth1 -m state — state RELATED,ESTABLISHED -j ACCEPT
$ sudo iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
好了,现在我们有流量通过,我们可以通过在局域网接口上插入任何东西并尝试 ping 某个互联网网站来测试流量。
我们保存它是为了让它在重启后仍然存在
sudo sh -c “iptables-save > /etc/iptables.ipv4.nat”
并将这一行添加到/etc/network/interfaces . d/et h1 . conf 中
up iptables-restore < /etc/iptables.ipv4.nat
配置 MAAS
先决条件
首先创建您的管理用户:
$ sudo maas createadmin — username=admin — [[email protected]](mailto:[email protected])
然后,让我们访问我们的 API 密钥,并从 CLI 登录:
$ sudo maas-region apikey — username=admin
有了这个命令的结果,只要做:
$ # maas login <profile-name> <hostname> <key>
$ maas login admin [http://localhost/MAAS/api/2.0](http://localhost/MAAS/api/2.0) <key>
或者你可以只输入一个命令:
$ sudo maas-region apikey — username=admin | \
maas login admin [http://localhost/MAAS/api/2.0](http://localhost/MAAS/api/2.0) -
现在,通过 GUI,在网络选项卡中,我们重命名我们的结构,以匹配 LAN、WAN 和 WLAN。
然后我们进入局域网,通过采取行动按钮,我们在上面启用 DHCP。
我们唯一要做的就是启动节点一次。它们将由 MAAS 直接处理,并在几分钟后出现在 GUI 中。
它们将有一个随机的名称,没有任何配置。
首先,我们将重新命名它们。为了方便起见,在我们的实验中,我们将使用 node00 和 node01 作为第一个非 gpu 节点,然后 node02 到 04 作为 GPU 节点。
命名后,我们还将
*标记它们仅 gpu 用于 2 个第一管理节点 标记它们gpu用于 4 个工作节点。 将电源方式设置为手动
然后我们有类似的东西

调试节点
这就是乐趣的开始。我们需要“委托节点”,换句话说,记录关于它们的信息(硬盘驱动器,CPU 计数…)
MAAS 中有一个 bug 阻碍了系统的部署。查看注释#14,并通过编辑/etc/Maas/preseeds/curtin _ user data来应用它。在重新启动部分添加一个延迟,使其看起来像:
power_state:
mode: reboot
delay: 30
然后,我们通过采取行动按钮进行调试,并选择调试,并取消选中所有其他 3 个选项。在此之后,我们手动为每个节点通电,MAAS 将完成剩下的工作,包括在流程结束时关闭它们。用户界面将看起来像这样:

MAAS commissioning nodes

MAAS from New To Commissioned
当调试成功时,我们会看到 HDD 大小、nb 内核和已填充内存的所有值,并且节点也变为就绪

MAAS Logs at commissiionning
使用 Juju 部署
引导环境
我们首先要做的是把 Juju 和 MAAS 联系起来。我们为作为提供者的 MAAS 创建一个配置文件 maas-juju.yaml ,内容为:
maas:
type: maas
auth-types: [oauth1]
endpoint: http://<MAAS_IP>/MAAS
将 MAAS_IP 地址理解为 Juju 与 MAAS 交互的地址,包括部署的节点。因此,您可以在我们的设置中使用 eth1 (192.168.23.1)
你可以在这一页找到更多细节
然后我们需要告诉 Juju 使用 MAAS:
$ juju add-cloud maas maas-juju.yaml
$ juju add-credential maas
Juju 需要引导启动第一个控制节点,该节点将托管 Juju 控制器、初始数据库和各种其他需求。这个节点是我们有两个管理节点的原因。第二个会是我们的 k8s 主。
在我们的设置中,我们的节点只有手动电源,因为 2.0 版的 MAAS 中删除了 WoL。这意味着我们需要触发引导,等待分配节点,然后手动启动它。
$ juju bootstrap maas-controller maas
Creating Juju controller “maas-controller” on maas
Bootstrapping model “controller”
Starting new instance for initial controller
Launching instance # This is where we start the node manually
WARNING no architecture was specified, acquiring an arbitrary node
— 4y3h8w
Installing Juju agent on bootstrap instance
Preparing for Juju GUI 2.2.2 release installation
Waiting for address
Attempting to connect to 192.168.23.2:22
Logging to /var/log/cloud-init-output.log on remote host
Running apt-get update
Running apt-get upgrade
Installing package: curl
Installing package: cpu-checker
Installing package: bridge-utils
Installing package: cloud-utils
Installing package: tmux
Fetching tools: curl -sSfw ‘tools from %{url_effective} downloaded: HTTP %{http_code}; time %{time_total}s; size %{size_download} bytes; speed %{speed_download} bytes/s ‘ — retry 10 -o $bin/tools.tar.gz <[[https://streams.canonical.com/juju/tools/agent/2.0-beta15/juju-2.0-beta15-xenial-amd64.tgz](https://streams.canonical.com/juju/tools/agent/2.0-beta15/juju-2.0-beta15-xenial-amd64.tgz)]>
Bootstrapping Juju machine agent
Starting Juju machine agent (jujud-machine-0)
Bootstrap agent installed
Bootstrap complete, maas-controller now available.
和 MAAS GUI:

初始捆绑包部署
我们部署下面的包文件 k8s.yaml :
series: xenial
services:
“kubernetes-master”:
charm: “cs:~containers/kubernetes-master-6”
num_units: 1
to:
— “0”
expose: true
annotations:
“gui-x”: “800”
“gui-y”: “850”
constraints: tags=cpu-only
flannel:
charm: “cs:~containers/flannel-5”
annotations:
“gui-x”: “450”
“gui-y”: “750”
easyrsa:
charm: “cs:~containers/easyrsa-3”
num_units: 1
to:
— “0”
annotations:
“gui-x”: “450”
“gui-y”: “550”
“kubernetes-worker”:
charm: “cs:~containers/kubernetes-worker-8”
num_units: 1
to:
— “1”
expose: true
annotations:
“gui-x”: “100”
“gui-y”: “850”
constraints: tags=gpu
etcd:
charm: “cs:~containers/etcd-14”
num_units: 1
to:
— “0”
annotations:
“gui-x”: “800”
“gui-y”: “550”
relations:
— — “kubernetes-master:kube-api-endpoint”
— “kubernetes-worker:kube-api-endpoint”
— — “kubernetes-master:cluster-dns”
— “kubernetes-worker:kube-dns”
— — “kubernetes-master:certificates”
— “easyrsa:client”
— — “kubernetes-master:etcd”
— “etcd:db”
— — “kubernetes-master:sdn-plugin”
— “flannel:host”
— — “kubernetes-worker:certificates”
— “easyrsa:client”
— — “kubernetes-worker:sdn-plugin”
— “flannel:host”
— — “flannel:etcd”
— “etcd:db”
machines:
“0”:
series: xenial
“1”:
series: xenial
我们可以看到,我们对节点进行了约束,强制 MAAS 为工作节点选择 GPU 节点,为主节点选择 CPU 节点。我们传递命令
$ juju deploy k8s.yaml
就是这样。这是让 k8s 正常运行所需的唯一命令!
added charm cs:~containers/easyrsa-3
application easyrsa deployed (charm cs:~containers/easyrsa-3 with the series “xenial” defined by the bundle)
added resource easyrsa
annotations set for application easyrsa
added charm cs:~containers/etcd-14
application etcd deployed (charm cs:~containers/etcd-14 with the series “xenial” defined by the bundle)
annotations set for application etcd
added charm cs:~containers/flannel-5
application flannel deployed (charm cs:~containers/flannel-5 with the series “xenial” defined by the bundle)
added resource flannel
annotations set for application flannel
added charm cs:~containers/kubernetes-master-6
application kubernetes-master deployed (charm cs:~containers/kubernetes-master-6 with the series “xenial” defined by the bundle)
added resource kubernetes
application kubernetes-master exposed
annotations set for application kubernetes-master
added charm cs:~containers/kubernetes-worker-8
application kubernetes-worker deployed (charm cs:~containers/kubernetes-worker-8 with the series “xenial” defined by the bundle)
added resource kubernetes
application kubernetes-worker exposed
annotations set for application kubernetes-worker
created new machine 0 for holding easyrsa, etcd and kubernetes-master units
created new machine 1 for holding kubernetes-worker unit
related kubernetes-master:kube-api-endpoint and kubernetes-worker:kube-api-endpoint
related kubernetes-master:cluster-dns and kubernetes-worker:kube-dns
related kubernetes-master:certificates and easyrsa:client
related kubernetes-master:etcd and etcd:db
related kubernetes-master:sdn-plugin and flannel:host
related kubernetes-worker:certificates and easyrsa:client
related kubernetes-worker:sdn-plugin and flannel:host
related flannel:etcd and etcd:db
added easyrsa/0 unit to machine 0
added etcd/0 unit to machine 0
added kubernetes-master/0 unit to machine 0
added kubernetes-worker/0 unit to machine 1
deployment of bundle “k8s.yaml” completed
在 GUI 中翻译为:

$ juju status
MODEL CONTROLLER CLOUD/REGION VERSION
default maas-controller maas 2.0-beta15APP VERSION STATUS EXPOSED ORIGIN CHARM REV OS
easyrsa 3.0.1 active false jujucharms easyrsa 3 ubuntu
etcd 2.2.5 active false jujucharms etcd 14 ubuntu
flannel 0.6.1 false jujucharms flannel 5 ubuntu
kubernetes-master 1.4.5 active true jujucharms kubernetes-master 6 ubuntu
kubernetes-worker active true jujucharms kubernetes-worker 8 ubuntuRELATION PROVIDES CONSUMES TYPE
certificates easyrsa kubernetes-master regular
certificates easyrsa kubernetes-worker regular
cluster etcd etcd peer
etcd etcd flannel regular
etcd etcd kubernetes-master regular
sdn-plugin flannel kubernetes-master regular
sdn-plugin flannel kubernetes-worker regular
host kubernetes-master flannel subordinate
kube-dns kubernetes-master kubernetes-worker regular
host kubernetes-worker flannel subordinateUNIT WORKLOAD AGENT MACHINE PUBLIC-ADDRESS PORTS MESSAGE
easyrsa/0 active idle 0 192.168.23.3 Certificate Authority connected.
etcd/0 active idle 0 192.168.23.3 2379/tcp Healthy with 1 known peers. (leader)
kubernetes-master/0 active idle 0 192.168.23.3 6443/tcp Kubernetes master running.
flannel/0 active idle 192.168.23.3 Flannel subnet 10.1.57.1/24
kubernetes-worker/0 active idle 1 192.168.23.4 80/tcp,443/tcp Kubernetes worker running.
flannel/1 active idle 192.168.23.4 Flannel subnet 10.1.67.1/24
kubernetes-worker/1 active executing 2 192.168.23.5 (install) Container runtime available.
kubernetes-worker/2 unknown allocating 3 192.168.23.7 Waiting for agent initialization to finish
kubernetes-worker/3 unknown allocating 4 192.168.23.6 Waiting for agent initialization to finishMACHINE STATE DNS INS-ID SERIES AZ
0 started 192.168.23.3 4y3h8x xenial default
1 started 192.168.23.4 4y3h8y xenial default
2 started 192.168.23.5 4y3ha3 xenial default
3 pending 192.168.23.7 4y3ha6 xenial default
4 pending 192.168.23.6 4y3ha4 xenial default
或者

在流程结束时,我们有:
$ juju status
MODEL CONTROLLER CLOUD/REGION VERSION
default maas-controller maas 2.0-beta15APP VERSION STATUS EXPOSED ORIGIN CHARM REV OS
cuda false local cuda 0 ubuntu
easyrsa 3.0.1 active false jujucharms easyrsa 3 ubuntu
etcd 2.2.5 active false jujucharms etcd 14 ubuntu
flannel 0.6.1 false jujucharms flannel 5 ubuntu
kubernetes-master 1.4.5 active true jujucharms kubernetes-master 6 ubuntu
kubernetes-worker 1.4.5 active true jujucharms kubernetes-worker 8 ubuntuRELATION PROVIDES CONSUMES TYPE
certificates easyrsa kubernetes-master regular
certificates easyrsa kubernetes-worker regular
cluster etcd etcd peer
etcd etcd flannel regular
etcd etcd kubernetes-master regular
sdn-plugin flannel kubernetes-master regular
sdn-plugin flannel kubernetes-worker regular
host kubernetes-master flannel subordinate
kube-dns kubernetes-master kubernetes-worker regular
host kubernetes-worker flannel subordinateUNIT WORKLOAD AGENT MACHINE PUBLIC-ADDRESS PORTS MESSAGE
easyrsa/0 active idle 0 192.168.23.3 Certificate Authority connected.
etcd/0 active idle 0 192.168.23.3 2379/tcp Healthy with 1 known peers. (leader)
kubernetes-master/0 active idle 0 192.168.23.3 6443/tcp Kubernetes master running.
flannel/0 active idle 192.168.23.3 Flannel subnet 10.1.57.1/24
kubernetes-worker/0 active idle 1 192.168.23.4 80/tcp,443/tcp Kubernetes worker running.
flannel/1 active idle 192.168.23.4 Flannel subnet 10.1.67.1/24
kubernetes-worker/1 active idle 2 192.168.23.5 80/tcp,443/tcp Kubernetes worker running.
flannel/2 active idle 192.168.23.5 Flannel subnet 10.1.100.1/24
kubernetes-worker/2 active idle 3 192.168.23.7 80/tcp,443/tcp Kubernetes worker running.
flannel/3 active idle 192.168.23.7 Flannel subnet 10.1.14.1/24
kubernetes-worker/3 active idle 4 192.168.23.6 80/tcp,443/tcp Kubernetes worker running.
flannel/4 active idle 192.168.23.6 Flannel subnet 10.1.83.1/24MACHINE STATE DNS INS-ID SERIES AZ
0 started 192.168.23.3 4y3h8x xenial default
1 started 192.168.23.4 4y3h8y xenial default
2 started 192.168.23.5 4y3ha3 xenial default
3 started 192.168.23.7 4y3ha6 xenial default
4 started 192.168.23.6 4y3ha4 xenial default
我们现在需要 kubectl 来查询集群。我们需要把这个 k8s 问题和 Hypriot OS 的方法联系起来
$ curl -s [https://packages.cloud.google.com/apt/doc/apt-key.gpg](https://packages.cloud.google.com/apt/doc/apt-key.gpg) | apt-key add -
$ cat <<EOF > /etc/apt/sources.list.d/kubernetes.list
deb [http://apt.kubernetes.io/](http://apt.kubernetes.io/) kubernetes-xenial main
EOF
$ apt-get update
$ apt-get install -y kubectl$ kubectl get nodes — show-labels
NAME STATUS AGE LABELS
node02 Ready 1h beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node02
node03 Ready 1h beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node03
node04 Ready 57m beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node04
node05 Ready 58m beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node05
添加 CUDA
CUDA 还没有正式的魅力,所以我写了一个 hacky bash 脚本让它工作,你可以在 GitHub 上找到
要建立魅力,你需要一台 x86 计算机,而不是 Rpi。你将需要 juju,魅力和魅力工具安装在那里,然后运行
$ export JUJU_REPOSITORY=${HOME}/charms
$ export LAYER_PATH=${JUJU_REPOSITORY}/layers
$ export INTERFACE_PATH=${JUJU_REPOSITORY}/interfaces$ cd ${LAYER_PATH}
$ git clone [https://github.com/SaMnCo/layer-nvidia-cuda](https://github.com/SaMnCo/layer-nvidia-cuda) cuda
$ cd juju-layer-cuda
$ charm build
这将在 JUJU_REPOSITORY 中创建一个名为 builds 的新文件夹,并在那里创建另一个名为 cuda 的文件夹。只需将它 scp 到你家 charms 子文件夹中的树莓 Pi 中。
$ scp ${JUJU_REPOSITORY}/builds/cuda ${USER}[@raspberrypi](http://twitter.com/raspberrypi):/home/${USER}/charms/cuda
$ git clone [https://github.com/SaMnCo/layer-nvidia-cuda](https://github.com/SaMnCo/layer-nvidia-cuda) cuda
$ cd juju-layer-cuda
$ charm build
来展示我们刚刚创造的魅力,
$ juju deploy — series xenial $HOME/charms/cuda
$ juju add-relation cuda kubernetes-worker
这将需要一些时间(CUDA 下载千兆字节的代码和二进制文件…),但最终我们会
$ juju status
MODEL CONTROLLER CLOUD/REGION VERSION
default maas-controller maas 2.0-beta15APP VERSION STATUS EXPOSED ORIGIN CHARM REV OS
cuda false local cuda 0 ubuntu
easyrsa 3.0.1 active false jujucharms easyrsa 3 ubuntu
etcd 2.2.5 active false jujucharms etcd 14 ubuntu
flannel 0.6.1 false jujucharms flannel 5 ubuntu
kubernetes-master 1.4.5 active true jujucharms kubernetes-master 6 ubuntu
kubernetes-worker 1.4.5 active true jujucharms kubernetes-worker 8 ubuntuRELATION PROVIDES CONSUMES TYPE
juju-info cuda kubernetes-worker regular
certificates easyrsa kubernetes-master regular
certificates easyrsa kubernetes-worker regular
cluster etcd etcd peer
etcd etcd flannel regular
etcd etcd kubernetes-master regular
sdn-plugin flannel kubernetes-master regular
sdn-plugin flannel kubernetes-worker regular
host kubernetes-master flannel subordinate
kube-dns kubernetes-master kubernetes-worker regular
juju-info kubernetes-worker cuda subordinate
host kubernetes-worker flannel subordinateUNIT WORKLOAD AGENT MACHINE PUBLIC-ADDRESS PORTS MESSAGE
easyrsa/0 active idle 0 192.168.23.3 Certificate Authority connected.
etcd/0 active idle 0 192.168.23.3 2379/tcp Healthy with 1 known peers. (leader)
kubernetes-master/0 active idle 0 192.168.23.3 6443/tcp Kubernetes master running.
flannel/0 active idle 192.168.23.3 Flannel subnet 10.1.57.1/24
kubernetes-worker/0 active idle 1 192.168.23.4 80/tcp,443/tcp Kubernetes worker running.
cuda/2 active idle 192.168.23.4 CUDA installed and available
flannel/1 active idle 192.168.23.4 Flannel subnet 10.1.67.1/24
kubernetes-worker/1 active idle 2 192.168.23.5 80/tcp,443/tcp Kubernetes worker running.
cuda/0 active idle 192.168.23.5 CUDA installed and available
flannel/2 active idle 192.168.23.5 Flannel subnet 10.1.100.1/24
kubernetes-worker/2 active idle 3 192.168.23.7 80/tcp,443/tcp Kubernetes worker running.
cuda/3 active idle 192.168.23.7 CUDA installed and available
flannel/3 active idle 192.168.23.7 Flannel subnet 10.1.14.1/24
kubernetes-worker/3 active idle 4 192.168.23.6 80/tcp,443/tcp Kubernetes worker running.
cuda/1 active idle 192.168.23.6 CUDA installed and available
flannel/4 active idle 192.168.23.6 Flannel subnet 10.1.83.1/24MACHINE STATE DNS INS-ID SERIES AZ
0 started 192.168.23.3 4y3h8x xenial default
1 started 192.168.23.4 4y3h8y xenial default
2 started 192.168.23.5 4y3ha3 xenial default
3 started 192.168.23.7 4y3ha6 xenial default
4 started 192.168.23.6 4y3ha4 xenial default
太棒了,我们现在有了 CUDERNETES !
我们可以在每个 GPU 节点上单独连接并运行
$ sudo nvidia-smi
Wed Nov 9 06:06:44 2016
+ — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -+
| NVIDIA-SMI 367.57 Driver Version: 367.57 |
| — — — — — — — — — — — — — — — -+ — — — — — — — — — — — + — — — — — — — — — — — +
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 GeForce GTX 106… Off | 0000:02:00.0 Off | N/A |
| 28% 31C P0 27W / 120W | 0MiB / 6072MiB | 0% Default |
+ — — — — — — — — — — — — — — — -+ — — — — — — — — — — — + — — — — — — — — — — — +
+ — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+ — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -+
干得好!!
在 Kubernetes 中启用 CUDA
默认情况下,当在 workers 上启动 API 服务器和 Kubelet 时,CDK 不会激活 GPU。我们需要手动完成这项工作(尽管这在路线图上)
主更新
在主节点上,更新/etc/default/kube-API server以添加:
# Security Context
KUBE_ALLOW_PRIV=” — allow-privileged=true”
然后通过以下方式重新启动 API 服务
$ sudo systemctl restart kube-apiserver
所以现在 Kube API 将接受运行特权容器的请求,这是 GPU 工作负载所需要的。
工作节点
在每个 worker 上, /etc/default/kubelet 来添加 GPU 标签,所以看起来像:
# Security Context
KUBE_ALLOW_PRIV=” — allow-privileged=true”# Add your own!
KUBELET_ARGS=” — experimental-nvidia-gpus=1 — require-kubeconfig — kubeconfig=/srv/kubernetes/config — cluster-dns=10.1.0.10 — cluster-domain=cluster.local”
然后通过以下方式重新启动服务
$ sudo systemctl restart kubelet
测试设置
现在我们已经在 k8s 中启用了 CUDA GPUs,让我们测试一下是否一切正常。我们采取了一个非常简单的工作,只是从一个 pod 运行 nvidia-smi,并在成功时退出。
工作定义是
apiVersion: batch/v1
kind: Job
metadata:
name: nvidia-smi
labels:
name: nvidia-smi
spec:
template:
metadata:
labels:
name: nvidia-smi
spec:
containers:
— name: nvidia-smi
image: nvidia/cuda
command: [ “nvidia-smi” ]
imagePullPolicy: IfNotPresent
securityContext:
privileged: true
resources:
requests:
alpha.kubernetes.io/nvidia-gpu: 1
limits:
alpha.kubernetes.io/nvidia-gpu: 1
volumeMounts:
— mountPath: /dev/nvidia0
name: nvidia0
— mountPath: /dev/nvidiactl
name: nvidiactl
— mountPath: /dev/nvidia-uvm
name: nvidia-uvm
— mountPath: /usr/local/nvidia/bin
name: bin
— mountPath: /usr/lib/nvidia
name: lib
volumes:
— name: nvidia0
hostPath:
path: /dev/nvidia0
— name: nvidiactl
hostPath:
path: /dev/nvidiactl
— name: nvidia-uvm
hostPath:
path: /dev/nvidia-uvm
— name: bin
hostPath:
path: /usr/lib/nvidia-367/bin
— name: lib
hostPath:
path: /usr/lib/nvidia-367
restartPolicy: Never
有趣的是
- 我们没有 nvidia-docker 提供的抽象,所以我们必须手动指定 char 设备的挂载点
- 我们还需要共享驱动程序和 libs 文件夹
- 在资源中,我们必须用 1 个 GPU 来请求和限制资源
- 容器必须在特权下运行
如果我们运行这个:
$ kubectl create -f nvidia-smi-job.yaml
$ # Wait for a few seconds so the cluster can download and run the container
$ kubectl get pods -a -o wide
NAME READY STATUS RESTARTS AGE IP NODE
default-http-backend-8lyre 1/1 Running 0 11h 10.1.67.2 node02
nginx-ingress-controller-bjplg 1/1 Running 1 10h 10.1.83.2 node04
nginx-ingress-controller-etalt 0/1 Pending 0 6m <none>
nginx-ingress-controller-q2eiz 1/1 Running 0 10h 10.1.14.2 node05
nginx-ingress-controller-ulsbp 1/1 Running 0 11h 10.1.67.3 node02
nvidia-smi-xjl6y 0/1 Completed 0 5m 10.1.14.3 node05
我们看到最后一个容器已经运行并完成。让我们看看运行的输出
$ kubectl logs nvidia-smi-xjl6y
Wed Nov 9 07:52:42 2016
+ — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -+
| NVIDIA-SMI 367.57 Driver Version: 367.57 |
| — — — — — — — — — — — — — — — -+ — — — — — — — — — — — + — — — — — — — — — — — +
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 GeForce GTX 106… Off | 0000:02:00.0 Off | N/A |
| 28% 33C P0 29W / 120W | 0MiB / 6072MiB | 0% Default |
+ — — — — — — — — — — — — — — — -+ — — — — — — — — — — — + — — — — — — — — — — — +
+ — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+ — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -+
完美,我们有相同的结果,就像我们从主机上运行 nvidia-smi 一样,这意味着我们都很好地操作 GPU!
结论
那么,我们在这里取得了什么成就呢?Kubernetes 是最通用的集装箱管理系统。它还与 Tensorflow 共享一些基因,tensor flow 本身经常以向外扩展的方式演示为容器。
通过大规模添加 GPU 来加快深度学习工作量是很自然的事情。这个穷人 GPU 集群是一个例子,说明了如果 R&D 的一个小团队想要尝试多节点可扩展性,他们可以做些什么。
我们还有第二个好处。你注意到 k8s 的部署在这里是完全自动化的(除了 GPU 包含),这要感谢 Juju 和 CDK 背后的团队。嗯,Juju 背后的社区创造了许多魅力,并且有大量可以大规模部署的横向扩展应用程序,如 Hadoop、Kafka、Spark、Elasticsearch (…)。
最终,投资只是 MAAS 和几个命令。Juju 在 R&D 的投资回报率是几天的事。
谢谢
非常感谢 Canonical 的 Marco Ceppi、Chuck Butler 和 Matt Bruzek 对 CDK 的出色工作,以及对我(众多)问题的回应。



