
云平台系统用户提出一个需求,要求根据物理机主机名或者IP查询其上虚拟机列表。根据主机名查询好办,nova的list接口提供了host参数;按主机IP查询就不那么直接了,需要先将IP反解析成主机名,然后使用主机名参数调用list接口。为了减少与其他系统的耦合,我们考虑nova自身能不能实现IP到主机名的转换。 实际上nova hypervisor数据结构对应的数据表compute_nodes中包含了host_ip字段,只是在查询时没有返回,稍作修改即可。 修改nova.api.openstack.compute.hypervisors.HypervisorsController._view_hypervisor()函数,给hyp_dict增加'host_ip': hypervisor.host_ip字段: 这个函数是hypervisor接口的基础,list和detail等接口都会调用这个接口。所以在这里增加host_ip字段后,调用list、detail接口返回的数据都包含host_ip了。 同时为了在nova hypervisor-list命令行结果中显示主机IP,需要对novaclient做一个小修改,在novaclient.v2.shell.do_hypervisor_list()函数中,columns定义了要展示的列,添加一个'Host IP'字段,它在取数据时会被转化成对应的host_ip属性: 此时执行nova hypervisor-list,就会看到包含了Host IP信息:
早前由于添加了全SSD的高性能Ceph集群,区别于现有的HDD集群,在OpenStack端需要能够选择使用两种集群。Cinder配置多Ceph后端的文档早已整理,整理文件夹时发现这篇为nova boot添加volume type参数的文档,也整理一下发出来。 Nova官方不支持创建虚拟机时指定volume type参数,是因为官方认为volume是由cinder管理的,nova不应该加以干涉,以保证功能的明确性和独立性。但创建虚拟机时却有指定创建volume的功能,到底添加一个参数是否无伤大雅,见仁见智。 不管怎么说,我们有这个需求,既然官方不支持,那就自己动手,丰衣足食。修改主要顺着创建虚拟机的整个流程进行,包括:命令行、API、DB、Compute。以下修改在Mitaka版本进行。 1. 命令行添加volume-type参数 修改novaclient.v2.shell.CLIENT_BDM2_KEYS,使novaclient支持volume-type参数: 2. API添加volume_type属性 修改nova.api.validation.parameter_types,增加volume_type: 修改nova.api.openstack.compute.schemas.block_device_mapping_v1. legacy_block_device_mapping,增加volume_type属性: 修改nova.block_device,给bdm_legacy_fields和bdm_new_fields增加volume_type: 修改nova.objects.block_device.BlockDeviceMapping,增加volume_type: 3. DB添加volume_type字段 修改nova.db.sqlalchemy.models.BlockDeviceMapping,增加volume_type: 4. Compute添加volume_type 修改nova.virt.block_device,给DriverVolumeBlockDevice、DriverImageBlockDevice和DriverBlankBlockDevice添加volume_type参数,并在调用volume_api创建卷的时候传入: 重启服务生效。然后在nova boot的--boot-volume参数中就可以指定volume_type属性了: # nova boot --flavor <flavor_id> --nic net-id=<net_id> --block-device id=<image_id>, source=image,dest=volume,device=vda,size=50,bootindex=0,shutdown=remove, volume_type=ceph-ssd test-01
1. 在Ceph上为Kubernetes创建一个文件系统 # ceph osd pool create cephfs_data 128 # ceph osd pool create cephfs_metadata 128 # ceph fs new cephfs cephfs_metadata cephfs_data 2. CephFS配置Quotas CephFS的mount方式分为内核态mount和用户态mount,内核态使用mount命令挂载,用户态使用ceph-fuse。内核态只有在kernel 4.17 + Ceph mimic以上的版本才支持Quotas,用户态则没有限制。 内核态mount: # mount -t ceph 10.32.3.70:/k8s_test /mnt/cephfs/ -o name=admin,secret=AQCs2Q9bqAjCHRAAlQUF+hAiXhbErk4NdtvORQ== 用户态mount: # ceph-fuse -r /k8s_test /mnt/cephfs/ --name client.admin 配置quota: 1) 首先在CephFS创建一个要限额的目录 # mkdir /mnt/cephfs # ceph-fuse /mnt/cephfs # mkdir /mnt/cephfs/k8s_test 2) 然后在目录上使用setfattr设置限额属性 # setfattr -n ceph.quota.max_bytes -v 100000000 /mnt/cephfs/k8s_test 比如上面这条命令限制/k8s_test目录只能使用100MB大小的空间。 3) 挂载限额目录并测试 # mkdir /mnt/k8s_test # ceph-fuse -r /k8s_test /mnt/k8s_test/ --name client.admin --client-quota 由于使用的内核和Ceph版本比较低,只能在用户态测试。在Jewel及之前版本的ceph-fuse,挂载时要指定--client-quota参数,限额才会生效。而在Luminous之后的版本,则不支持这个参数了,会自动识别quota。由于我这里使用的是Jewel版本,所以指定了--client-quota参数。然后写入200MB数据测试一下: # cd /mnt/k8s_test # dd if=/dev/zero of=test1.bin bs=1M count=200 dd: error writing ‘test1.bin’: Disk quota exceeded 129+0 records in 128+0 records out 134348800 bytes (134 MB) copied, 0.428233 s, 314 MB/s # df -h Filesystem Size Used Avail Use% Mounted on ceph-fuse 92M -64Y -36M 100% /mnt/cephfs 可以看到中途提示超出配额,但是写入了134MB数据,超过了指定的配额100MB。这是因为CephFS的Quotas不是严格的,按官方说法判断检测周期是10s。 再次写入数据,可以发现完全写不进去了: # dd if=/dev/zero of=test2.bin bs=1M count=200 dd: error writing ‘test2.bin’: Disk quota exceeded 1+0 records in 0+0 records out 0 bytes (0 B) copied, 0.00162182 s, 0.0 kB/s 3. 更新k8s用户权限 # ceph auth caps client.k8s mon 'allow rwx' osd 'allow rwx pool=k8s, allow rw pool=cephfs_data' mds 'allow rwp' 如果还没有创建k8s用户,则使用下面的命令创建: # ceph auth get-or-create client.k8s mon 'allow rwx' osd 'allow rwx pool=k8s, allow rw pool=cephfs_data' mds 'allow rwp' -o ceph.client.k8s.keyring 在Kubernets中创建访问Ceph的Secret的步骤见《Kubernetes配置Ceph RBD StorageClasses作为Persistent Volumes Claims后端》第3~5步。 4. Kubernetes Volume使用CephFS Kubernetes Volume原生支持CephFS类型,使用方法: # echo ‘apiVersion: v1 kind: Pod metadata: name: nginx-test-cephfs spec: containers: - name: nginx-test-cephfs image: registry.exmaple.com/base/nginx:v1.0 volumeMounts: - name: cephfs mountPath: "/data/" volumes: - name: cephfs cephfs: monitors: - 10.32.3.70:6789 - 10.32.3.71:6789 - 10.32.3.72:6789 path: /k8s_test user: k8s secretRef: name: ceph-k8s-secret readOnly: false’ | kubectl create -f - 进入容器查看: # kubectl exec nginx-test-cephfs -it -- /bin/bash [root@nginx-test-cephfs ~]# df -h Filesystem Size Used Avail Use% Mounted on 10.32.3.70:6789,10.32.3.71:6789,10.32.3.72:6789:/k8s_test 5.2T 1.1G 5.2T 1% /data 5. Kubernetes Persistent Volume使用CephFS Kubernetes Persistent Volume原生支持CephFS类型,使用方法: # echo 'apiVersion: v1 kind: PersistentVolume metadata: name: nginx-test-cephfs-pv-01 namespace: test spec: capacity: storage: 100Mi accessModes: - ReadWriteMany cephfs: monitors: - 10.32.3.70:6789 - 10.32.3.71:6789 - 10.32.3.72:6789 path: /k8s_test user: k8s secretRef: name: ceph-k8s-secret readOnly: false --- kind: PersistentVolumeClaim apiVersion: v1 metadata: name: nginx-test-cephfs-pvc-01 spec: accessModes: - ReadWriteMany resources: requests: storage: 50Mi --- apiVersion: v1 kind: Pod metadata: name: nginx-test-cephfs-01 spec: containers: - name: nginx-test-cephfs image: registry.exmaple.com/base/nginx:v1.0 volumeMounts: - name: cephfs-vol1 mountPath: "/data/" volumes: - name: cephfs-vol1 persistentVolumeClaim: claimName: nginx-test-cephfs-pvc-01' | kubectl create -f - 进入容器查看: # kubectl exec nginx-test-cephfs -it -- /bin/bash [root@nginx-test-cephfs ~]# df -h Filesystem Size Used Avail Use% Mounted on 10.32.3.70:6789,10.32.3.71:6789,10.32.3.72:6789:/k8s_test 5.2T 1.1G 5.2T 1% /data 6. Kubernetes StorageClass使用CephFS Kubernetes StorageClass原生不支持CephFS,但是在社区的孵化项目External Storage中添加了CephFS类型的StorageClass。External Storage是对核心的Kubernetes controller manager的扩展,其中包含的每个external provisioner可以独立部署以支持扩展的StorageClass类型。 部署CephFS external provisioner: 1) 下载External Storage代码 # go get github.com/kubernetes-incubator/external-storage 同时要保证CephFS external provisioner依赖的package源码存在于$GOPATH/src目录下,如果有缺失,后面编译会报错,按照报错使用go get下载就行。 2) 编译、打包镜像、上传镜像 进入CephFS external provisioner的源码目录: # cd $GOPATH/src/github.com/kubernetes-incubator/external-storage/ceph/cephfs 修改Makefile,将REGISTRY变量改成自己的镜像仓库地址,注意结尾要带/: # vim Makefile ifeq ($(REGISTRY),) REGISTRY = registry.example.com/ endif 修改Dockerfile,指定CEPH_VERSION与自己Ceph Cluster的版本一致: # vim Dockerfile ENV CEPH_VERSION "mimic" 编译,生成cephfs-provisioner二进制文件: # make 打包docker镜像,并上传到镜像仓库: # make push 3) 部署CephFS external provisioner 进入部署目录: # cd $GOPATH/src/github.com/kubernetes-incubator/external-storage/ceph/cephfs/deploy 修改镜像地址: # vim rbac/deployment.yaml image: "registry.example.com/cephfs-provisioner:latest" 修改希望部署到的namespace: # NAMESPACE=kube-system # sed -r -i "s/namespace: [^ ]+/namespace: $NAMESPACE/g" ./rbac/*.yaml 部署: # kubectl -n $NAMESPACE apply -f ./rbac 4) 创建cephfs StorageClass # echo ‘apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: cephfs provisioner: ceph.com/cephfs parameters: monitors: 10.32.3.70:6789,10.32.3.71:6789,10.32.3.72:6789 adminId: k8s adminSecretName: ceph-k8s-secret adminSecretNamespace: kube-system‘ | kubectl create -f - 5) 创建一个PersistentVolumeClaim # echo ‘apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-test-cephfs-pvc-03 annotations: volume.beta.kubernetes.io/storage-class: "cephfs" spec: accessModes: - ReadWriteMany resources: requests: storage: 100Mi’ | kubectl create -f - 6) 创建使用PVC的Pod # echo ‘apiVersion: v1 kind: Pod metadata: name: nginx-test-cephfs-03 spec: containers: - name: nginx-test-cephfs-03 image: registry.example.com/base/nginx:v1.0 volumeMounts: - name: pvc mountPath: "/data/" volumes: - name: pvc persistentVolumeClaim: claimName: nginx-test-cephfs-pvc-03’ | kubectl create -f - 7) 查看容器状态 # kubectl exec nginx-test-cephfs-03 -it -- /bin/bash # df -h Filesystem Size Used Avail Use% Mounted on 10.32.3.70:6789,10.32.3.71:6789,10.32.3.72:6789:/volumes/kubernetes/kubernetes-dynamic-pvc-6bfb0ff3-6980-11e8-aa58-5a90b87a7c36 5.2T 1.1G 5.2T 1% /data 7. 为CephFS StorageClass添加Quotas支持 注意容器中CephFS的挂载方式,使用的是内核态mount,挂载目录的容量并不是PVC声明的100MiB,而是整个CephFS的可用大小。 查看源码发现在Kubernetes 1.10之后的版本才会尝试用户态mount。而且目前使用的Kubernetes 1.10.4版本存在bug,ceph-fuse挂载时指定了-k参数,却没有指定-i或者-n参数,导致尝试使用ceph-fuse挂载会报错,最终后还是回退到内核态挂载。 另外目前CephFS external provisioner中创建目录时,并没有指定ceph.quota.max_bytes属性,为了添加配额限制,需要修改代码。 1) 修改CephFS external provisioner CephFS external provisioner的结构比较简单,主要就是cephfs-provisioner.go和cephfs_provisioner.py两个文件。cephfs-provisioner.go是主程序,创建CephFS Provisioner。cephfs_provisioner.py 是对ceph_volume_client.CephFSVolumeClient的封装,方便调用。 修改思路就是在CephFS中创建目录时指定size参数,底层的CephFSVolumeClient.create_volume()判断size存在时会调用setattr设置ceph.quota.max_bytes属性。 具体修改内容可以参考这个PR。修改完后,重新执行第6步中的打包部署操作使之生效。 2) 修改Kubelet 由于1.10.4版本的kubelet存在bug,使用ceph-fuse挂载目录时指定了-k参数却没有指定-i或者-n参数,导致挂载报错。需要修改代码,加上-i参数。具体位置是kubernetes/pkg/volume/cephfs/cephfs.go的execFuseMount()函数,原来的mount参数: func (cephfsMounter *cephfsMounter) checkFuseMount() bool { … mountArgs := []string{} mountArgs = append(mountArgs, "-k") mountArgs = append(mountArgs, keyring_file) mountArgs = append(mountArgs, "-m") mountArgs = append(mountArgs, src) mountArgs = append(mountArgs, mountpoint) mountArgs = append(mountArgs, "-r") mountArgs = append(mountArgs, cephfsVolume.path) … } 追加两行: mountArgs = append(mountArgs, "--id") mountArgs = append(mountArgs, cephfsVolume.id) 如果使用的ceph-fuse版本低于Luminous,还要加上--client-quota参数: mountArgs = append(mountArgs, "--client-quota") 然后重新编译kubelet,替换安装文件,重启服务生效。编译方法参考《编译Kubelet二进制文件》。 3) 验证Quota 重新使用第6步的配置创建一个Pod,进入容器查看: # kubectl exec nginx-test-cephfs-03 -it -- /bin/bash root@nginx-test-cephfs-03:/# df -h Filesystem Size Used Avail Use% Mounted on ceph-fuse 100M 0 100M 0% /data 看到挂载方式是ceph-fuse,目录可用大小也是quota配置的100M。写入数据测试一下: # dd if=/dev/zero of=/data/test.bin bs=1M count=200 dd: error writing '/data/test.bin': Disk quota exceeded 123+0 records in 122+0 records out 128421888 bytes (128 MB, 122 MiB) copied, 4.32033 s, 29.7 MB/s # df -h Filesystem Size Used Avail Use% Mounted on ceph-fuse 100M 100M 0 100% /data Quota确实生效了。 参考资料 CephFS Admin Tips – Create a new user and share kubernetes笔记: Cephfs
1. 在Ceph上为Kubernetes创建一个存储池 # ceph osd pool create k8s 128 2. 创建k8s用户 # ceph auth get-or-create client.k8s mon 'allow r' osd 'allow rwx pool=k8s' -o ceph.client.k8s.keyring 3. 将k8s用户的key进行base64编码 这是Kubernetes访问Ceph的密钥,会保存在Kubernetes的Secret中 # grep key ceph.client.k8s.keyring | awk '{printf "%s", $NF}' | base64 VBGFaeN3OWJYdUZPSHhBQTNrU2E2QlUyaEF5UUV0SnNPRHdXeRT8PQ== 4. 在Kubernetes创建访问Ceph的Secret # echo ' apiVersion: v1 kind: Secret metadata: name: ceph-k8s-secret type: "kubernetes.io/rbd" data: key: VBGFaeN3OWJYdUZPSHhBQTNrU2E2QlUyaEF5UUV0SnNPRHdXeRT8PQ== --- apiVersion: v1 kind: Secret metadata: name: ceph-admin-secret namespace: kube-system type: "kubernetes.io/rbd" data: key: VBGFaeN3OWJYdUZPSHhBQTNrU2E2QlUyaEF5UUV0SnNPRHdXeRT8PQ== ‘ | kubectl create -f - 5. 将访问Ceph的keyring复制到Kubernetes work节点上 在创建Pod的时候,kubelet会调用rbd命令去检测和挂载PVC对应的rbd镜像,因此在kubelet节点上要保证存在rbd命令和访问ceph的keyring。否则创建Pod,kubelet有可能报各种各样ceph相关的错误。 如果kubelet在worker节点上是正常运行在default namespace下的,那么安装ceph-common包,然后将keyring拷贝到/etc/ceph/目录下即可;如果kubelet是运行在容器中,那这两个操作就需要在容器中执行。 我们的环境中,kubelet是运行在rkt容器中的,官方镜像中已经包含了ceph客户端,所以只需要将keyring拷贝到容器中。 我们环境中使用systemctl管理kubelet,以服务的方式启动一个rkt容器来运行kubelet,修改/etc/systemd/system/kubelet.service,增加一个对应keyring的volume: [Unit] Description=Kubernetes Kubelet Documentation=https://github.com/kubernetes/kubernetes After=load-images.service Requires=docker.service [Service] EnvironmentFile=/etc/cluster-envs Environment=KUBELET_IMAGE_TAG=v1.7.10 Environment="RKT_RUN_ARGS= \ --volume ceph-keyring,kind=host,source=/etc/ceph/ceph.client.k8s.keyring \ --mount volume=ceph-keyring,target=/etc/ceph/ceph.client.k8s.keyring \ --volume modprobe,kind=host,source=/usr/sbin/modprobe \ --mount volume=modprobe,target=/usr/sbin/modprobe \ --volume lib-modules,kind=host,source=/lib/modules \ --mount volume=lib-modules,target=/lib/modules \ ExecStartPre=/usr/bin/mkdir -p /etc/ceph ExecStart=/opt/bin/kubelet-wrapper \ --address=0.0.0.0 \ --allow-privileged=true \ --cluster-dns=192.168.192.10 \ --cluster-domain=cluster.local \ --cloud-provider='' \ --port=10250 \ --lock-file=/var/run/lock/kubelet.lock \ --exit-on-lock-contention \ --node-labels=worker=true \ --pod-manifest-path=/etc/kubernetes/manifests \ --kubeconfig=/etc/kubernetes/kubeconfig.yaml \ --require-kubeconfig=true \ --network-plugin=cni \ --cni-conf-dir=/etc/cni/net.d \ --cni-bin-dir=/opt/cni/bin \ --logtostderr=true Restart=always RestartSec=10 [Install] WantedBy=multi-user.target 6. 在Kubernetes创建ceph-rbd StorageClass # echo ‘apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: ceph-rbd provisioner: kubernetes.io/rbd parameters: monitors: 10.32.24.11:6789,10.32.24.12:6789,10.32.24.13:6789 adminId: k8s adminSecretName: ceph-k8s-secret adminSecretNamespace: kube-system pool: k8s userId: k8s userSecretName: ceph-k8s-secret’ | kubectl create -f - 7. 将ceph-rbd设置为默认的StorageClass # kubectl patch storageclass ceph-rbd -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' 注意集群中只能存在一个默认StorageClass,如果同时将多个StorageClass设置为默认,相当于没有设置默认StorageClass。查看StorageClass列表,默认StorageClass带有(default)标记: # kubectl get storageclass NAME TYPE ceph-rbd (default) kubernetes.io/rbd ceph-sas kubernetes.io/rbd ceph-ssd kubernetes.io/rbd 8. 创建一个PersistentVolumeClaim # echo ‘apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-test-vol1-claim spec: accessModes: - ReadWriteOnce storageClassName: ceph-rbd resources: requests: storage: 10Gi’ | kubectl create -f - 因为指定了默认StorageClass,所以这里的storageClassName其实可以省略。 9. 创建使用PVC的Pod # echo ‘apiVersion: v1 kind: Pod metadata: name: nginx-test spec: containers: - name: nginx image: nginx:latest volumeMounts: - name: nginx-test-vol1 mountPath: /data/ readOnly: false volumes: - name: nginx-test-vol1 persistentVolumeClaim: claimName: nginx-test-vol1-claim’ | kubectl create -f - 10. 查看容器状态 进入容器看到rbd挂载到了/data目录 # kubectl exec nginx-test -it -- /bin/bash [root@nginx-test ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/rbd0 50G 52M 47G 1% /data
1. 环境 系统:CentOS 7.2 Go:1.10.3 Kubernetes:1.10.4 2. 安装最新版go 编译的Kubernetes 1.10.4要求go版本在1.9.3以上,使用下面的yum源安装最新版go: [golang] name=Golang baseurl=https://mirror.go-repo.io/centos/7/x86_64/ enable=1 gpgcheck=0 # yum install golang 3. 下载Kubernetes源码到$GOPATH目录 # go get -d k8s.io/kubernetes 如果访问k8s.io有问题,可以从github下载,然后复制到$GOPATH/src/k8s.io/目录。 4. 编译 进入kubernetes目录,切换到要编译的版本,然后编译: # cd $GOPATH/src/k8s.io/kubernetes # git checkout tags/v1.10.4 # make clean # make WHAT=cmd/kubelet WHAT指定只编译kubelet,减少编译时间。编译完成后会在_output/bin/目录下生成kubelet二进制文件。 5. 更新 将编译生成的二进制文件复制到安装目录,重启服务就完成更新了: # cp $GOPATH/src/k8s.io/kubernetes/_output/bin/kubelet /usr/local/bin/kubelet # systemctl restart kubelet 如果kubelet是运行在容器中,则需要打包成镜像,参考Building Kubernetes。 参考资料 编译 Kubernetes 二进制文件