在《使用Ceph RBD为Kubernetes集群提供存储卷》一文中,我们提到:借助Kubernetes和Ceph的集成,Kubernetes可以使用Ceph RBD为集群内的Pod提供Persistent Volume。但这一过程中,RBD所使用的image的创建、删除还需要手动管理,于是我们又基于go-ceph实现了对RBD image的程序化管理,我们的最终目标是要这种对RBD image的管理服务以一个K8s service的形式发布到Kubernetes集群中去,这就是本文标题中描述的那样:Kuberize Ceph RBD API服务。
一、Dockerize Ceph RBD API服务
要想使得ceph rbd api Kuberizable,首先要Dockerize Ceph RBD API Service,即容器化。由于go-ceph是Go语言开发,我们的rbd-rest-api同样用Go语言开发。使用Go语言开发有一个众所周知的好处,那就是可以编译为静态二进制文件,可以在运行时不依赖任何外部库,生来自带“适合容器”标签。但由于go-ceph是一个go binding for librados和librbd,其通过cgo实现Go语言对C库的链接和调用。这样一来,我们如果要做static linking,那么我们就要准备齐全所有librados和librbd所依赖的第三方库的.a(archive file)。如果你仅仅是执行下面编译命令,你将得到w行级别的错误信息输出:
$ go build --ldflags '-extldflags "-static"' .
从错误的信息中,我们可以得到rbd-rest-api静态编译依赖的各种第三方库,包括boost库(apt-get install libboost-all-dev)、libssl(apt-get install libssl)以及libnss3(apt-get install libnss3-dev)。安装好这些库,再修改一下命令行,可将编译错误输出降低到百行以内:
# go build --ldflags '-extldflags "-static -L /usr/lib/x86_64-linux-gnu -lboost_system -lboost_thread -lboost_iostreams -lboost_random -lcrypto -ldl -lpthread -lm -lz -lc -L /usr/lib/gcc/x86_64-linux-gnu/4.8/ -lstdc++"' .
不过,你将依旧得到诸多错误:
... ...
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../lib/librados.a(Crypto.o): In function `CryptoAESKeyHandler::init(ceph::buffer::ptr const&, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&)':
/build/ceph-10.2.3/src/auth/Crypto.cc:280: undefined reference to `PK11_GetBestSlot'
/build/ceph-10.2.3/src/auth/Crypto.cc:291: undefined reference to `PK11_ImportSymKey'
/build/ceph-10.2.3/src/auth/Crypto.cc:304: undefined reference to `PK11_ParamFromIV'
/build/ceph-10.2.3/src/auth/Crypto.cc:282: undefined reference to `PR_GetError'
/build/ceph-10.2.3/src/auth/Crypto.cc:293: undefined reference to `PR_GetError'
... ...
这些”undefined reference”指向的符号都是libnss3-dev库中的,但由于libnss3-dev的安装并没有包含libnss3.a文件,因此即便将libnss3显式放在链接参数列表中,比如:”-lnss3″也无法链接成功:
/usr/bin/ld: cannot find -lnss3
libnss库着实不是一个省油灯,经过几番折腾发现,要想使用libnss的static archive,我们只能手工编译,代码在这里可以获取到:https://github.com/nss-dev/nss,并且这里提供了nss的手工编译方法。
综上可以看出,纯静态编译rbd-rest-api是很繁琐的,于是我们这次选择默认的动态链接方式,我们只需在docker image中安装librados和librbd这两个依赖库即可,于是rbd-rest-api的Dockerfile的雏形可见:
From ubuntu:14.04
MAINTAINER Tony Bai <author@xxx.com>
# use aliyun source for ubuntu
# before building image ,make sure copy /etc/apt/sources.list here
# COPY sources.list /etc/apt/
RUN apt-get update && apt-get install -y --no-install-recommends librados-dev librbd-dev \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /root/rbd-rest-api
COPY rbd-rest-api /root/rbd-rest-api
COPY conf /root/rbd-rest-api/conf
RUN chmod +x /root/rbd-rest-api/rbd-rest-api
EXPOSE 8080
WORKDIR /root/rbd-rest-api
ENTRYPOINT ["/root/rbd-rest-api/rbd-rest-api"]
我们一直在Ubuntu 14.04.x环境下进行各种测试,于是我们自然而然的选择ubuntu:14.04作为我们的base image,构建镜像:
# docker build -t "test/rbd-rest-api" .
... ...
Setting up librados-dev (0.80.11-0ubuntu1.14.04.1) ...
Setting up librbd-dev (0.80.11-0ubuntu1.14.04.1) ...
Processing triggers for libc-bin (2.19-0ubuntu6.9) ...
---> c987abc7a24d
Removing intermediate container 5257ac37392a
Step 5 : RUN mkdir -p /root/rbd-rest-api
---> Running in dcabdb990c60
---> ce0db2a027aa
Removing intermediate container dcabdb990c60
Step 6 : COPY rbd-rest-api /root/rbd-rest-api
---> 453fd4b9a27a
Removing intermediate container 8b07b5de7537
Step 7 : COPY conf /root/rbd-rest-api/conf
---> e956add07d60
Removing intermediate container 6eaf6e4cf334
Step 8 : RUN chmod +x /root/rbd-rest-api/rbd-rest-api
---> Running in cb278d1919c7
---> 1e7b86072011
Removing intermediate container cb278d1919c7
Step 9 : EXPOSE 8080
---> Running in 6a3f457eefca
---> e60cefb50f77
Removing intermediate container 6a3f457eefca
Step 10 : WORKDIR /root/rbd-rest-api
---> Running in 703baf8c5564
---> 6f1a5e5e145c
Removing intermediate container 703baf8c5564
Step 11 : ENTRYPOINT /root/rbd-rest-api/rbd-rest-api
---> Running in 16dd4e7e3995
---> 43f885b958c7
Removing intermediate container 16dd4e7e3995
Successfully built 43f885b958c7
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/rbd-rest-api latest 43f885b958c7 57 seconds ago 298 MB
测试启动镜像,注意我们“只读”挂载了本地路径/etc/ceph:
# docker run --name rbd-rest-api --rm -p 8080:8080 -v /etc/ceph/:/etc/ceph/:ro test/rbd-rest-api
2016/11/14 14:58:17 [I] [asm_amd64.s:2086] http server Running on http://:8080
我们来测试一下这个Docker中的rbd-rest-api service:
# curl -v http://localhost:8080/api/v1/pools/
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/v1/pools/ HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 130
< Content-Type: application/json; charset=utf-8
* Server beegoServer:1.7.1 is not blacklisted
< Server: beegoServer:1.7.1
< Date: Mon, 14 Nov 2016 14:59:29 GMT
<
{
"Kind": "PoolList",
"APIVersion": "v1",
"Items": [
{
"name": "rbd"
},
{
"name": "rbd1"
}
]
* Connection #0 to host localhost left intact
}
测试OK。
这里不得不提的是:如果你挂载的是仅仅是/etc/ceph/ceph.conf的话,那么当rbd-rest-api服务收到请求后,会返回:
Errcode=300, errmsg:
error rados: No such file or directory
这是因为容器中的rbd-rest-api没有看到ceph.client.admin.keyring,因此在登录ceph monitor时鉴权失败了。当然你也可以不映射本地目录,取而代之的是将/etc/ceph/ceph.conf和/etc/ceph/ceph.client.admin.keyring放入到镜像中,后一种方法这里就不详细描述了。librados给出的错误提示真是太差了,本来应该是一个权限的问题,居然说找不到librados。
二、Kuberize Ceph RBD API服务
容器化测试成功了,接下来就是将Ceph RBD API Kuberize化。根据上面Docker镜像的设计,承载Ceph RBD API服务 Pod的Node上,必须要安装了Ceph client,即包括ceph.conf和ceph.client.admin.keyring,于是有选择性的调度Ceph RBD API服务到安装了ceph client的kubernetes node上是这一节必须考虑的问题。
我们的思路是将rbd-rest-api的pod通过k8s调度到带有指定label的k8s node上去,我们给kubernetes集群的node打标签,安装了ceph client的集群node,打的标签为:zone=ceph。
# kubectl label nodes 10.46.181.146 zone=ceph
# kubectl label nodes 10.47.136.60 zone=ceph
# kubectl get nodes --show-labels
NAME STATUS AGE LABELS
10.46.181.146 Ready 32d beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10.46.181.146,zone=ceph
10.47.136.60 Ready 32d beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10.47.136.60,zone=ceph
接下来就是在rbd-rest-api service的yaml中设定pod的调度策略了:
//rbd-rest-api.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: rbd-rest-api
spec:
replicas: 2
template:
metadata:
labels:
app: rbd-rest-api
spec:
containers:
- name: rbd-rest-api
image: registry.cn-hangzhou.aliyuncs.com/xxxx/rbd-rest-api:latest
#imagePullPolicy: IfNotPresent
imagePullPolicy: Always
ports:
- containerPort: 8080
volumeMounts:
- mountPath: /etc/ceph
name: ceph-default-config-volume
volumes:
- name: ceph-default-config-volume
hostPath:
path: /etc/ceph
nodeSelector:
zone: ceph
imagePullSecrets:
- name: rbd-rest-api-default-secret
---
apiVersion: v1
kind: Service
metadata:
name: rbd-rest-api
labels:
app: rbd-rest-api
spec:
ports:
- port: 8080
selector:
app: rbd-rest-api
我们可以看到在Deployment的spec中有一个nodeSelector,这个设置可以让k8s scheduler在调度service时只选择具备zone=ceph label的Node。注意关于imagePullSecrets的设置,可以参考《Kubernetes从Private Registry中拉取容器镜像的方法》一文。
评论