存档

文章标签 ‘OpenStack’

OpenStack Magnum 初体验

2017年1月9日 1 条评论

OpenStack 的容器历史

自容器技术变为目前云计算最火热和最前沿的技术以来,OpenStack 作为一个云计算管理平台(亦或是云计算生态系统), 怎么将容器管理纳入到其内,成为了近两年 OpenStack 发展的一个热门方向。

OpenStack 的容器整合之路先后经历了以下几种历程:

os_container.png

目前 MagnumMurano 是 OpenStack 中血统最正的容器项目。

可以通过 gitstatus 的分析结果看一下 magnum 的开发活跃度

os_magnum_commits_by_year.png

os_magnum_month_of_year.png

由上面的分析可以看到,magnum 目前开发非常活跃。

Magnum 介绍

Magnum 利用 Keystone, Nova, Heat, Neutron 等已有的 OpenStack组件,整合容器的 集群管理系统如 kubernetes, Mesos 等, 为用户更加简单灵活,多租户的容器服务.

在 Magnum 中的设计中,容器和虚拟机一样为一等公民, 有自己独立的逻辑实现,而不是像 nova docker driver 那样仅仅是 nova 的后端实现。

目前 Magnum 主要为用户提供以下功能:

  • 容器管理集群的部署: 支持 Kubernetes, Docker Swarm, Mesos 这三种容器管理集群类型的部署
  • 容器管理集群的管理: 对容器管理集群进行弹性扩容, 修改配置,提供访问容器管理集群的认证信息

Magnum 的主要概念:

  • Bay: 表示一个集群,比如一个 Mesos 集群,新版本中已经改为 cluster
  • BayModel: 集群的 flavor, 类似于虚拟机的 flavor, BayModel 定义 Docker 集群的一些规格, 比如节点使用的 k8s 或者 mesos 镜像, 网络参数等. 新版本中已经改为 ClusterTemplate
  • Cluser: 同 bay
  • ClusterTemplate: 同 BayModel
  • Node: 指 bay 中的某个节点,即 nova 中的虚拟机, 容器在该 node 里面运行.
  • COE: Container Orchestration Engine, 容器集群管理后端,目前支持的有 k8s, Swarm 和 Mesos. magnum 的主要工作就是将用户的请求转发到 COE, 完成容器的管理。

以下是 k8s 中的一些概念扫盲(下文中的演示部分使用 k8s 来举例)

  • Pod: k8s 的部署调度单元, 一般多个容器组成一个 Pod, Pod 里的容器运行相同的应用, 属于同一 Pod的容器运行在同一个Minion(Host)上,作为一个统一管理单元,共享相同的 volume 和 network namespace/IP和Port空间。详细信息参考 k8s 中的介绍。在新版本中 Pod 的相关管理 命令已经从 magnum 中移除, 要管理 Pod 需要使用 k8s 提供的 kubectl 工具。
  • Replication controller: 用来保证用户定义数量的 Pod 副本在任何时候都是处于 正常运行状态的。也即他的职责是用来管理 Pod 的伸缩
  • Service: 可以理解为是pod网关,因为pod在运行中可能被删除或者ip发生变化,service可以 保证pod的动态变化对访问端是透明的。

800px-Magnum_architecture.png

安装

可以采用 devstack 或者手动安装的方式安装,下面是使用 devstack 安装 magnum 部分的相关配置

# magnum 需要 heat 来编排调度 node
enable_plugin heat http://git.trystack.cn/openstack/heat.git
ENABLED_SERVICES+=,h-eng,h-api,h-api-cfn,h-api-cw
 
# magnum 需要负载均衡组件来做集群管理,特别的,这里使用 v2 的 lbaas 服务
enable_plugin neutron-lbaas http://git.trystack.cn/openstack/neutron-lbaas.git
enable_plugin octavia http://git.trystack.cn/openstack/octavia
disable_service q-lbaas
ENABLED_SERVICES+=,q-lbaasv2
ENABLED_SERVICES+=,octavia,o-cw,o-hk,o-hm,o-api
 
# 启用 magnum 和 magnum-u 插件
enable_plugin magnum http://git.trystack.cn/openstack/magnum.git
enable_plugin magnum-ui http://git.trystack.cn/openstack/magnum-ui.git

安装完成后,可以通过以下命令确认 magnum 服务正常运行。

$ magnum service-list
+----+-----------------------+------------------+-------+----------+-----------------+---------------------------+---------------------------+
| id | host                  | binary           | state | disabled | disabled_reason | created_at                | updated_at                |
+----+-----------------------+------------------+-------+----------+-----------------+---------------------------+---------------------------+
| 1  | localhost.localdomain | magnum-conductor | up    |          | -               | 2017-01-03T10:48:14+00:00 | 2017-01-04T06:45:33+00:00 |
+----+-----------------------+------------------+-------+----------+-----------------+---------------------------+---------------------------+

创建容器管理集群

在容器的世界中,容器是运行在操作系统之上的特殊的 “应用程序”, 由在此操作系统上运行的容器管理程序 k8s 或者 mesos 管理. 在 magnum 中,这个操作系统(和管理程序)是 nova 中的虚拟机。不难理解这样使用 带来的好处,可以复用 nova,glance,cinder,neutron 等等的功能。反正容器本身只需要一个运行时的操作 系统,不管这个操作系统实在虚拟机还是物理机上。

创建容器管理集群的规格

类似于虚拟机的使用,在创建容器管理集群的时候,需要有一个规格,也即上面说过的 ClusterTemplate, 根据此规格的定义来创建容器管理集群。

$ nova keypair-add --pub-key ~/.ssh/id_rsa.pub testkey
$ magnum cluster-template-create \
  --name k8s-cluster-template \          <-- 规格的名字
  --image fedora-atomic-latest \         <-- 运行 node 的镜像
  --keypair testkey \                    <-- 导入一个密钥到 k8s node 上, 方便后续的访问
  --external-network public \            <-- 给每个 node 分配浮动 IP时用到的外部网络
  --dns-nameserver 192.168.2.64 \        <-- node 里面的 dns 配置, 我这里选用一台内网的地址,是为了防止集群初始化的时候访问 google 的服务失败,在这里做翻墙处理 :)
  --flavor m1.small \                    <-- node 虚拟机的规格
  --docker-volume-size 5 \               <-- 给 docker 预留的 volume 的大小
  --network-driver flannel \             <-- 容器使用的网络后端,这里选用 flannel
  --coe kubernetes                       <-- 容器的管理后端,这里选择 k8s

创建 k8s 集群

$ magnum cluster-create --name k8s-cluster --cluster-template k8s-cluster-template --node-count 2
 
$ magnum cluster-list # 等到 status 变为 CREATE_COMPLETE 说明集群创建成功。
+--------------------------------------+-------------+---------+------------+--------------+-----------------+
| uuid                                 | name        | keypair | node_count | master_count | status          |
+--------------------------------------+-------------+---------+------------+--------------+-----------------+
| dd483a45-2b7a-4294-b918-680e1399a7cc | k8s-cluster | testkey | 2          | 1            | CREATE_COMPLETE |
+--------------------------------------+-------------+---------+------------+--------------+-----------------+
 
$ magnum cluster-show k8s-cluster
+---------------------+------------------------------------------------------------+
| Property            | Value                                                      |
+---------------------+------------------------------------------------------------+
| status              | CREATE_COMPLETE                                            |
| cluster_template_id | 8538544b-c809-478f-a98d-e0c35bfeef9c                       |
| node_addresses      | ['192.168.5.136', '192.168.5.134']                         |        <-- k8s 的 minion node
| uuid                | dd483a45-2b7a-4294-b918-680e1399a7cc                       |
| stack_id            | b9e815a8-a746-49b0-a307-755c17415324                       |
| status_reason       | Stack CREATE completed successfully                        |
| created_at          | 2017-01-04T09:07:45+00:00                                  |
| updated_at          | 2017-01-04T09:10:05+00:00                                  |
| coe_version         | v1.2.0                                                     |
| keypair             | testkey                                                    |
| api_address         | https://192.168.5.137:6443                                 |        <-- k8s api endpoint
| master_addresses    | ['192.168.5.137']                                          |        <-- k8s 的master node
| create_timeout      | 60                                                         |
| node_count          | 2                                                          |
| discovery_url       | https://discovery.etcd.io/6a6004c6a9730be2e0896e412caea0a9 |
| master_count        | 1                                                          |
| container_version   | 1.9.1                                                      |
| name                | k8s-cluster                                                |
+---------------------+------------------------------------------------------------+

由于 magnum 后端使用 heat 来做集群的编排,所以也可以通过 heat 来查看相关状态:

$ heat stack-list
+--------------------------------------+--------------------------+-----------------+----------------------+--------------+
| id                                   | stack_name               | stack_status    | creation_time        | updated_time |
+--------------------------------------+--------------------------+-----------------+----------------------+--------------+
| 5fb85534-54fe-407d-ac2d-f3c319024735 | k8s-cluster-644272c22n6h | CREATE_COMPLETE | 2017-01-05T02:28:43Z | None         |
+--------------------------------------+--------------------------+-----------------+----------------------+--------------+

在 horizon 上也可以看到用到的资源的拓扑图:

os_magnum_ui.png

配置集群的访问方式

注入 k8s 的访问密钥到 k8s 集群中,这里利用 magnum 提供的 cluster-config 命令, 该命令会生成三个 *.pem证书文件和一个 k8s 客户端的配置文件 config, 并将相应的公钥注入到 k8s 中. 将这四个文件拷贝到任何想访问 k8s 集群的终端即可。

# Set kubectl to use the correct certs
$ magnum cluster-config k8s-cluster
$ ls *.pem config
ca.pem  cert.pem  config  key.pem

使用容器管理集群

严格来讲上面已经完成了容器管理集群的部署,照着 k8s 的文档就可以开始使用容器了。

安装配置 k8s 客户端

$ wget https://github.com/kubernetes/kubernetes/releases/download/v1.0.1/kubernetes.tar.gz
$ tar -xvzf kubernetes.tar.gz
$ sudo cp -a kubernetes/platforms/linux/amd64/kubectl /usr/bin/kubectl

拷贝 k8s 的证书文件到当前目录下,再导入 k8s 的配置文件即可开始使用,例如下面的实例:

$ export KUBECONFIG=config
$ kubectl cluster-info
Kubernetes master is running at https://192.168.5.131:6443
KubeUI is running at https://192.168.5.131:6443/api/v1/proxy/namespaces/kube-system/services/kube-ui

创建容器集群

我们以 k8s 源码目录 kubernetes/examples/redis 下的 redis 集群为例子。

$ kubectl create -f redis-master.yaml
$ kubectl create -f redis-sentinel-service.yaml
$ kubectl create -f redis-controller.yaml
$ kubectl create -f redis-sentinel-controller.yaml

创建完成之后可以看到所有资源的状态都就绪了。

$ kubectl get pods,rc,services
NAME                   READY     STATUS    RESTARTS   AGE
redis-master           2/2       Running   0          2h
redis-ptvgd            1/1       Running   0          1h
redis-sentinel-6wiha   1/1       Running   0          3m
CONTROLLER       CONTAINER(S)   IMAGE(S)              SELECTOR              REPLICAS
redis            redis          kubernetes/redis:v1   name=redis            2
redis-sentinel   sentinel       kubernetes/redis:v1   redis-sentinel=true   2
NAME             LABELS                                    SELECTOR              IP(S)          PORT(S)
kubernetes       component=apiserver,provider=kubernetes   <none>                10.254.0.1     443/TCP
redis-sentinel   name=sentinel,role=service                redis-sentinel=true   10.254.38.28   26379/TCP

验证我们创建的redis集群:

首先进入容器中设置一个 key:

$ kubectl exec redis-master -ti /bin/bash
root@redis-master:/data# redis-cli  
127.0.0.1:6379> set var "hello world"
OK

找另外一台机器作为 redis 客户端

dunrong@localhost$ kubectl port-forward -p redis-master 6380:6379                <-- 将 k8s 中 redis 的服务端口(6379) 端口映射到本地6380
I0110 17:56:55.657227    2903 portforward.go:225] Forwarding from 127.0.0.1:6380 -> 6379
I0110 17:56:55.657335    2903 portforward.go:225] Forwarding from [::1]:6380 -> 6379

然后用 redis-cli 访问:

dunrong@localhost$ redis-cli -p 6380
127.0.0.1:6380> GET var
"hello world"
127.0.0.1:6380>

that’s it!

小结

从上面的介绍和 demo 来看,目前用 magnum 作为容器的方案是完全可行的。 但由于容器在 OpenStack 中还属于快速开发阶段,在我看来他的作用目前仅仅是一个 k8s, mesos 之类的的容器管理集群的部署脚本,且在使用中存在各种各样的 bug。如果用户具有 一定的 OpenStack 和容器技术的经验,那么使用 OpenStack 在生产中管理部署容器是可行的。

tips

在 k8s 初始化的时候,默认情况下件会到 google的镜像仓库去下载一些 docker 镜像, 由于 你懂的 的原因,在大中华区这步肯定会失败。解决方案无外乎是让整个集群都能 科学上网 , 我这里使用一种最简单的方式。在创建 ClusterTemplate 的时候,指定 DNS 为另外一台机器,并这台机器上安装 dnsmasq, 将 gcr 的域名请求做本地解决即可。

分类: Docker, OpenStack 标签: ,

Cinder 的私有卷类型功能的使用

2015年7月9日 没有评论

数据库升级

如果是在之前部署的 Juno 上升级的,通过 backport 改功能来实现的,就使用下列指令 升级一下数据库。

[root@localhost ~]# cinder-manage db sync

使用

下面以一个例子来说明一下怎么使用该功能. 使用的后端存储是 lvm 和 ceph 集群里面的 pool。

基本配置

用户 租户 卷类型 后端卷类型
demo1 tenant1 volume_type1 rbd1
demo2 tenant2 volume_type2 rbd2

创建租户

为了比较,这里创建两个租户 demo1 和 demo2 和两个用户 demo1(属于 tenant1), demo2(属于 tenant2):

创建两个租户 tenant1 和 tenant2:

[root@localhost ~]# keystone tenant-create --name tenant1
+-------------+----------------------------------+
|   Property  |              Value               |
+-------------+----------------------------------+
| description |                                  |
|   enabled   |               True               |
|      id     | 38132b7bf32b434398862971c567bca3 |
|     name    |             tenant1              |
+-------------+----------------------------------+
[root@localhost ~]# keystone tenant-create --name tenant2
+-------------+----------------------------------+
|   Property  |              Value               |
+-------------+----------------------------------+
| description |                                  |
|   enabled   |               True               |
|      id     | 3fb70cdab54c4322a277f9acaa360e71 |
|     name    |             tenant2              |
+-------------+----------------------------------+

再分别创建两个用户 demo1 和 demo2:

[root@localhost ~]# keystone user-create --name demo1 --tenant tenant1 --pass demo1
+----------+----------------------------------+
| Property |              Value               |
+----------+----------------------------------+
|  email   |                                  |
| enabled  |               True               |
|    id    | 2e69d91577e949509ce20069786526c5 |
|   name   |              demo1               |
| tenantId | 38132b7bf32b434398862971c567bca3 |
| username |              demo1               |
+----------+----------------------------------+
[root@localhost ~]# keystone user-create --name demo2 --tenant tenant2 --pass demo2
+----------+----------------------------------+
| Property |              Value               |
+----------+----------------------------------+
|  email   |                                  |
| enabled  |               True               |
|    id    | 7687d7a33e49464cb7cedfbf20e5781c |
|   name   |              demo2               |
| tenantId | 3fb70cdab54c4322a277f9acaa360e71 |
| username |              demo2               |
+----------+----------------------------------+

创建 rbd pool

创建 pool rbd1, rbd2

[root@localhost ~]# ceph osd pool create rbd1 128
pool 'rbd1' created
[root@localhost ~]# ceph osd pool create rbd2 128
pool 'rbd2' created

查看创建的 pool

[root@localhost ~]# rados lspools
data
metadata
rbd
images
rbd1
rbd2

增加 cinder 的卷后端

编辑 cinder 的配置文件(一般为 /etc/cinder/cinder.conf):

enabled_backends=lvm,rbd2,rbd1
[rbd1]
volume_backend_name=rbd1
volume_driver=cinder.volume.drivers.rbd.RBDDriver
rbd_pool=rbd1
rbd_max_clone_depth=5
rbd_user=admin
rbd_flatten_volume_from_snapshot=False
rbd_ceph_conf=/etc/ceph/ceph.conf
rbd_secret_uuid=7d65f135-d7ba-4d87-a083-910ff8cf4eb2
 
[rbd2]
volume_backend_name=rbd2
volume_driver=cinder.volume.drivers.rbd.RBDDriver
rbd_pool=rbd2
rbd_max_clone_depth=5
rbd_user=admin
rbd_flatten_volume_from_snapshot=False
rbd_ceph_conf=/etc/ceph/ceph.conf
rbd_secret_uuid=7d65f135-d7ba-4d87-a083-910ff8cf4eb2

重启 cinder 服务

[root@localhost ~]# systemctl restart openstack-cinder-scheduler
[root@localhost ~]# systemctl restart openstack-cinder-volume
[root@localhost ~]# systemctl restart openstack-cinder-api

创建私有卷类型

注意使用的 cinderclient 必须是 V2 版本,如果是 RestAPI,也必须访问 V2 的cinder,可以使用下列指令设置命令行的版本号:

export OS_VOLUME_API_VERSION=2

创建两种私有的卷类型: volume_type1, volume_type

[root@localhost ~]# cinder type-create volume_type1 --is-public false
+--------------------------------------+--------------+-------------+-----------+
|                  ID                  |     Name     | Description | Is_Public |
+--------------------------------------+--------------+-------------+-----------+
| 71fa330c-5401-47ea-9e2d-96466ec7d3bd | volume_type1 |             |   False   |
+--------------------------------------+--------------+-------------+-----------+
[root@localhost ~]# cinder type-create volume_type2 --is-public false
+--------------------------------------+--------------+-------------+-----------+
|                  ID                  |     Name     | Description | Is_Public |
+--------------------------------------+--------------+-------------+-----------+
| f4030afb-3300-47c0-97e4-debfcacbcf82 | volume_type2 |             |   False   |
+--------------------------------------+--------------+-------------+-----------+
[root@localhost ~]# cinder type-list --all
+--------------------------------------+--------------+-------------+-----------+
|                  ID                  |     Name     | Description | Is_Public |
+--------------------------------------+--------------+-------------+-----------+
| 71fa330c-5401-47ea-9e2d-96466ec7d3bd | volume_type1 |             |   False   |
| f4030afb-3300-47c0-97e4-debfcacbcf82 | volume_type2 |             |   False   |
+--------------------------------------+--------------+-------------+-----------+

将 volume_type1 和 volume_type2 的后端存储分别设置为 rbd1 和 rbd2:

注意: 查看卷的类型只能需要加 –all 参数

[root@localhost ~]# cinder type-key 71fa330c-5401-47ea-9e2d-96466ec7d3bd set volume_backend_name=rbd1
[root@localhost ~]# cinder type-key f4030afb-3300-47c0-97e4-debfcacbcf82 set volume_backend_name=rbd2
[root@localhost ~]# . keystonerc_admin; cinder type-list --all
+--------------------------------------+--------------+-------------+-----------+
|                  ID                  |     Name     | Description | Is_Public |
+--------------------------------------+--------------+-------------+-----------+
| 71fa330c-5401-47ea-9e2d-96466ec7d3bd | volume_type1 |             |   False   |
| f4030afb-3300-47c0-97e4-debfcacbcf82 | volume_type2 |             |   False   |
+--------------------------------------+--------------+-------------+-----------+

将卷类型指定给租户

将卷 volume_type1 和 volume_type2 分别指定给 tenant1 和 tenant2, 注意这里 只能使用 UUID,不能使用名字

[root@localhost ~]# cinder type-access-add --volume-type 71fa330c-5401-47ea-9e2d-96466ec7d3bd --project-id 38132b7bf32b434398862971c567bca3
[root@localhost ~]# cinder type-access-add --volume-type f4030afb-3300-47c0-97e4-debfcacbcf82 --project-id 3fb70cdab54c4322a277f9acaa360e71
[root@localhost ~]# cinder type-access-list --volume-type 71fa330c-5401-47ea-9e2d-96466ec7d3bd
+--------------------------------------+----------------------------------+
|            Volume_type_ID            |            Project_ID            |
+--------------------------------------+----------------------------------+
| 71fa330c-5401-47ea-9e2d-96466ec7d3bd | 38132b7bf32b434398862971c567bca3 |
+--------------------------------------+----------------------------------+
[root@localhost ~]# cinder type-access-list --volume-type f4030afb-3300-47c0-97e4-debfcacbcf82
+--------------------------------------+----------------------------------+
|            Volume_type_ID            |            Project_ID            |
+--------------------------------------+----------------------------------+
| f4030afb-3300-47c0-97e4-debfcacbcf82 | 3fb70cdab54c4322a277f9acaa360e71 |
+--------------------------------------+----------------------------------+

创建卷

使用 demo1 创建类型为 volume_type1 的卷

[root@localhost ~]# . keystonerc_demo1; cinder create 1 --volume-type volume_type1
+---------------------------------------+--------------------------------------+
|                Property               |                Value                 |
+---------------------------------------+--------------------------------------+
|           availability_zone           |                 nova                 |
|                   id                  | 4e4fad1f-26aa-4690-9268-6628d01138e8 |
|                  name                 |                 None                 |
|      os-vol-tenant-attr:tenant_id     |   38132b7bf32b434398862971c567bca3   |
|                  size                 |                  1                   |
|                 status                |               creating               |
|                user_id                |   2e69d91577e949509ce20069786526c5   |
|              volume_type              |             volume_type1             |
+---------------------------------------+--------------------------------------+
[root@localhost ~]# rbd -p rbd1 ls
volume-4e4fad1f-26aa-4690-9268-6628d01138e8

使用 demo2 创建类型为 volume_type2 的卷

[root@localhost ~]# . keystonerc_demo2; cinder create 1 --volume-type volume_type2
+---------------------------------------+--------------------------------------+
|                Property               |                Value                 |
+---------------------------------------+--------------------------------------+
|           availability_zone           |                 nova                 |
|                   id                  | 3366b9d9-7437-45d1-bbef-bc59127785e6 |
|                  name                 |                 None                 |
|      os-vol-tenant-attr:tenant_id     |   3fb70cdab54c4322a277f9acaa360e71   |
|                 status                |               creating               |
|                user_id                |   7687d7a33e49464cb7cedfbf20e5781c   |
|              volume_type              |             volume_type2             |
+---------------------------------------+--------------------------------------+
[root@localhost ~]# rbd -p rbd2 ls
volume-3366b9d9-7437-45d1-bbef-bc59127785e6

分别使用 demo1, demo2 创建默认类型的卷

[root@localhost ~]# . keystonerc_demo1; cinder create 1
+---------------------------------------+--------------------------------------+
|                Property               |                Value                 |
+---------------------------------------+--------------------------------------+
|           availability_zone           |                 nova                 |
|                   id                  | 6d2811e9-3ca6-4993-801a-ff59783ef891 |
|                  name                 |                 None                 |
|      os-vol-tenant-attr:tenant_id     |   38132b7bf32b434398862971c567bca3   |
|                  size                 |                  1                   |
|                 status                |               creating               |
|                user_id                |   2e69d91577e949509ce20069786526c5   |
|              volume_type              |             volume_type1             |
+---------------------------------------+--------------------------------------+
[root@localhost ~]# rbd -p rbd1 ls
volume-4e4fad1f-26aa-4690-9268-6628d01138e8
volume-6d2811e9-3ca6-4993-801a-ff59783ef891
[root@localhost ~]# . keystonerc_demo2; cinder create 1
+---------------------------------------+--------------------------------------+
|                Property               |                Value                 |
+---------------------------------------+--------------------------------------+
|           availability_zone           |                 nova                 |
|                   id                  | ae3466e2-3ed3-4b6a-be9b-0dd485f99da0 |
|                  name                 |                 None                 |
|      os-vol-tenant-attr:tenant_id     |   3fb70cdab54c4322a277f9acaa360e71   |
|                  size                 |                  1                   |
|                 status                |               creating               |
|                user_id                |   7687d7a33e49464cb7cedfbf20e5781c   |
|              volume_type              |             volume_type2             |
+---------------------------------------+--------------------------------------+
[root@localhost ~]# rbd -p rbd2 ls
volume-3366b9d9-7437-45d1-bbef-bc59127785e6
volume-ae3466e2-3ed3-4b6a-be9b-0dd485f99da0

测试分别使用 demo1 创建 volume_type2, 使用 demo2 创建 volume_type1 的卷

[root@localhost ~]# . keystonerc_demo1; cinder create 1 --volume-type volume_type2
ERROR: Not Found (HTTP 404) (Request-ID: req-c749eb4e-d4dd-4bfb-aa62-26f8b8a4f986)
[root@localhost ~]# . keystonerc_demo2; cinder create 1 --volume-type volume_type1
ERROR: Not Found (HTTP 404) (Request-ID: req-255b8249-f260-4b36-a2c0-0a5aa28e74e3)

查看卷类型

可以看到,除了管理员用户,用户只能看到各自租户的卷类型,不能看到其他租户的卷类型

[root@localhost ~]# . keystonerc_demo1; cinder type-list
+--------------------------------------+--------------+-------------+-----------+
|                  ID                  |     Name     | Description | Is_Public |
+--------------------------------------+--------------+-------------+-----------+
| 71fa330c-5401-47ea-9e2d-96466ec7d3bd | volume_type1 |             |   False   |
+--------------------------------------+--------------+-------------+-----------+
[root@localhost ~]# . keystonerc_demo2; cinder type-list
+--------------------------------------+--------------+-------------+-----------+
|                  ID                  |     Name     | Description | Is_Public |
+--------------------------------------+--------------+-------------+-----------+
| f4030afb-3300-47c0-97e4-debfcacbcf82 | volume_type2 |             |   False   |
+--------------------------------------+--------------+-------------+-----------+

查看卷

可以看到,除了管理员用户,用户只能看到各自租户的卷,不能看到其他租户的卷

[root@localhost ~]# . keystonerc_demo1; cinder list
+--------------------------------------+-----------+------+------+--------------+----------+-------------+
|                  ID                  |   Status  | Name | Size | Volume Type  | Bootable | Attached to |
+--------------------------------------+-----------+------+------+--------------+----------+-------------+
| 4e4fad1f-26aa-4690-9268-6628d01138e8 | available | None |  1   | volume_type1 |  false   |             |
| 6d2811e9-3ca6-4993-801a-ff59783ef891 | available | None |  1   | volume_type1 |  false   |             |
+--------------------------------------+-----------+------+------+--------------+----------+-------------+
[root@localhost ~]# . keystonerc_demo2; cinder list
+--------------------------------------+-----------+------+------+--------------+----------+-------------+
|                  ID                  |   Status  | Name | Size | Volume Type  | Bootable | Attached to |
+--------------------------------------+-----------+------+------+--------------+----------+-------------+
| 3366b9d9-7437-45d1-bbef-bc59127785e6 | available | None |  1   | volume_type2 |  false   |             |
| ae3466e2-3ed3-4b6a-be9b-0dd485f99da0 | available | None |  1   | volume_type2 |  false   |             |
+--------------------------------------+-----------+------+------+--------------+----------+-------------+

分类: OpenStack 标签:

Cinder Private Volume Type(租户卷类型) 在大规模部署中带来的好处

2015年7月9日 没有评论

问题的提出

在 OpenStack 部署中,一般将不同存储类型,不同性能规格的存储资源形成一个大的集群,对外提供云硬盘的服务,这个大的集群统一由 cinder 来管理。但是在实践中,往往会遇到一些问题。

我们知道所有的计算节点可以按照 Zone 来划分,把计算机点按机房(机柜)的位置纳入到一个 Zone 中,如果其中一个机房(机柜)因为某种原因出问题了,不会影响其它机房/柜的虚拟机和网络。 而且, 同属于一个 Zone 的云主机在发生租户内部的流量的时候,就不会超出这个 Zone 的网络,租户网络的性能得到最大的利用和提升。

不过对于存储的访问,如果没有类似于这种隔离的技术,就会造成存储网络不可控,存储流量在各个存储交换机之间乱跑。

另一方面,根据不同的要求往往划分为多种不同的类型的云硬盘:例如:

  • 根据不同的后端存储
  • 创建不同性能的云硬盘
  • 为了开发新技术,创建一些测试类型的存储卷

但是所有的存储类型对租户都是可见的,用户创建磁盘的时候可以任意选择云硬盘类型,哪怕这些类型并不希望被某些用户使用。更糟糕的是,用户创建基于新的云硬盘启动的云主机的时候,这个新的云硬盘的类型是随机的!

情况在 Kilo 版本带来的名为 Private Type(私有卷) 的功能得到改善。该功能允许创建租户私有的云硬盘类型,这些类型的云硬盘指定为只能被某些租户访问。

解决方式

综上,对之前提到的两个问题,就有了比较好的解决方案。下面以使用一个 Ceph 作为存储后端为例说明:

  • 首先,从物理层面上,将一个 Zone 内的计算节点的存储网络和适当的 Ceph 节点的存储网络放在同一个网络设备下(比如同一个交换机)。
  • 然后,创建一个 Ceph 集群的资源池,指定资源池包含上面的 Ceph 节点。
  • 最后,根据上面的资源池创建对应的的云硬盘类型,并设置改类型为私有的,将它的访问权限赋予适当的租户。

至此,没有被赋予访问权限的租户将看不到这些存储。完成了实际上的存储隔离。

需要注意的地方

注意:即使是 Kilo 版本的 OpenStack,也没有完善该功能, 需要对创建云硬盘部分进行改动,确保在 nova 创建基于云硬盘启动的云主机的时候,只能创建具有权限的云主机。

参考

具体改功能的用法请参考 这里 的干货

分类: OpenStack 标签:

Glance 源码分析(3) – WSGI 框架

2014年10月27日 没有评论

Table of Contents

Server

OpenStack 大多数模块封装了 eventlet 以及 eventlet.wsgi.server 组成一个 class Server 来作为一个 HTTP RestFul API Server. 咋一看他的封装好像很复杂, 但是如果了解 eventlet 的基本概念, 这部分代码其实非常简单.

eventlet 在它内部定义的 greenthread(绿色线程) 里, 它利用协程来实现并发. 协程的介绍可以看我以前写的文章: Coroutine(协程) 介绍

为了彻底实现高性能的 I/O 并发, eventlet 甚至把底层的一些 API 都做了修改, .e.g socket, listen. 更多的 eventlet 的内容不在这讨论, 可以去 官网 查看相关的文档

下面是一个简单的小例子利用 eventlet 构建一个 HTTP Server, 基本上就是 glance 的 class Server 的核心骨架:

class APP(object):
    @webob.dec.wsgify
    def __call__(self, req):
        return 'hello world'
 
# 创建一个绑定在本机 8000 端口 的 socket.
sock = eventlet.listen(('0.0.0.0', 8000))
# 启动 wsgi server
eventlet.wsgi.server(sock, APP())

that’s it, so easy. 把他保存到文件并运行, 就是一个简单的 Http server, 并且 我可以付责任的告诉你, openstack 中的 WSGI 服务就是这么运行的.

所以我们回过头看 glance.common.wsgi.py:Server 这个类, 它只不过包装了一下 eventlet 的参数, 并且 WSGI 实例是在子进程里面运行的, 支持多进程运行该服务(多核考虑), 对外提供了 Server:start()Server:wait() 两个 API, 其他的, 就真没什么了.

paste 的工厂函数

请先复习一下 paste 的相关内容. 然后看下面的图:

API 框架

图中每一个 filter 都对应着一个工厂函数(可调用的实例), 比如:

[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory

对应的实例都是 Middleware 其实是的子类, 前面我们讲过, paste配置里面的工厂函数 所创造的是一个能调用的实例(包含__call__方法), 当收到用户请求的时候就会自动调用该 实例, 也就是调用 __call__ 方法. 这里 openstack 所有模块抽象除了一个类 Middleware, 封装了 process_request 方法. 每个 filter 子类只需要继承 该类, 如果需要做处理, 就覆盖 process_request 方法, 然后 Middleware 里面 的 __call__ 方法会根据 process_request 的返回值来判断是否交给下一个 filter 处理.

# file: glance/api/middleware/version_negotiation.py
class VersionNegotiationFilter(wsgi.Middleware):
class Middleware(object):
    def __init__(self, application):
        self.application = application
 
    @classmethod
    def factory(cls, global_conf, **local_conf):
        def filter(app):
            return cls(app)
        return filter
 
    def process_request(self, req):
        return None
 
    def process_response(self, response):
        return response
 
    @webob.dec.wsgify
    def __call__(self, req):
        # 首先调用子类的 process_request 方法, 如果子类没有实现这个方法或者
        # 返回值不为空, 那么直接将子类的返回回复给用户, 否则进行下一个 filter
        # 的处理. 这其实是一个递归的过程, 最后返回从下游(filter或app)的到的返回
        # 给用户
        response = self.process_request(req)
        if response:
            return response
        response = req.get_response(self.application)
        response.request = req
        try:
            return self.process_response(response)
        except webob.exc.HTTPException as e:
            return e

分类: OpenStack 标签:

Glance 源码分析(2) – 配置文件

2014年10月25日 没有评论

这里我们会分析 glance-api 读取以下两个配置文件

  • glance-api.conf: glance-api 的用户配置文件
  • glance-api-paste.ini: glance-api 的 WSGI 配置文件

glance-api.conf

该配置文件的读取是利用 oslo 模块来实现的, oslo 提供了 .ini 格式的配置 文件的解析, 被所有 OpenStack 模块用来解析配置文件.

oslo 的用法很简单, 下面举个简单的例子:

from oslo.config import cfg
 
default_opts = [
    cfg.StrOpt('bind_host',
               default='0.0.0.0',
               help='IP address to listen on'),
    cfg.IntOpt('bind_port',
               default=9292,
               help='Port number to listen on')
]
 
app_opt = cfg.StrOpt('name',
                     default='blog',
                     help='name of this app')
 
# cfg.CONF 是在 oslo.cfg 模块中的一个全局变量, 首先我们需要得到一个它的引用
# 然后调用 register_opt() 注册我们需要解析的配置项, 或者使用 register_opts()
# 同时注册多个配置项
# 如果配置文件中可以找到配置项, 那么使用配置项中的值, 不然使用注册该配置项时指定
# 的默认值
CONF = cfg.CONF
CONF.register_opt(app_opt, group='app')
CONF.register_opts(default_opts)
CONF(default_config_files=['app.conf'])
 
# 使用的时候可以用 CONF.cfgname 来使用, 如果该 cfgname 不在 [DEFAULT] 段下,
# 那么使用 CONF.section.cfgname 来引用
print CONF.items()
print CONF.app.name
print CONF.bind_host
print CONF.bind_port
# file: app.conf
[DEFAULT]
bind_port = 8080
[app]
name = test
# python test.py 
[('bind_port', 8080), ('config_dir', None), ('config_file', ['app.conf']), ('bind_host', '0.0.0.0'), ('app', <oslo.config.cfg.GroupAttr object at 0x7fa4a3d75c50>)]
0.0.0.0
8080
test

那么问题来了… 前面讲过, glance 中的配置文件通过 config.parse_args() 来调用的, 也就是所有的配置都是在 glance.common.config:parse_args() 中完成的, 通过上面的分析, 这个函数其实很简单, 这里就不深入探讨了. 总之, 以后, glance 的各个模块在想访问用户提供的配置的时候, 只需要使用类似以下的代码就可以了:

from oslo.config import cfg
CONF = cfg.CONF
print CONF.enable_v2_api

glance-api-paste.ini

该内容和 glance 无关, 只要搞懂了 python paste 模块的用法, 就很简单了. 关于 paste 模块的使用说明, 请参考 python paste.deploy 探索

下面把之前在框架那里的内容再贴一份:

API 框架

[pipeline:glance-api-keystone]
pipeline = versionnegotiation authtoken context rootapp
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
delay_auth_decision = true
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app

分类: OpenStack 标签:

Glance 源码分析(1) – 框架

2014年10月24日 没有评论

以下主要分析 V2 版本的代码, V1 和 V2的最大区别就是:

  • V1: 包含两个服务 glance-api 和 glance-registry. glance-api 接受客户端的 所有命令, 分发并响应, 涉及到数据库的操作由内部转发到 glance-registry 完成
  • V2 简化了流程, 所有的处理都在内部实现, 不需要额外的服务. 因此只需要 glance-api 一个服务

启动流程

启动流程

# glance/cmd/api.py
def main():
    # 载入配置, 下面的函数在 glance/common/config.py 中定义, 调用此函数会初始化
    # oslo 模块中的模块变量 CONF, 把 glance-api.conf 中的值设置在 CONF 的属性中,
    # 使用的时候按照 "CONF.enable_v1_api" 此方式调用, 具体的细节不多讲
    config.parse_args()
 
    # 初始化后端存储,
    # 1. 将所有后端存储的类名注册到 glance/store/__init__.py:REGISTERED_STORES
    # 2. 将所有后端存储的名字和实例注册到 glance/store/location.py:SCHEME_TO_CLS_MAP
    #    以后 glance-api 可以根据用户的请求和配置文件找到具体的后端存储的实例, 调用相应的
    #    实例的函数(add/delete)来操作
    glance.store.create_stores()
 
    # 启动 WSGI 程序
    # load_paste_app 会在默认位置(/etc/glance/)找到 glance-api-paste.ini,
    # 然后根据用户的配置(是否启用 keystone等)调用 paste.loadapp 载入相应的 app,
    # 最后传递给 server.start 启动 WSGI 应用, 具体的细节之后的系列会讲到
    server = wsgi.Server()
    server.start(config.load_paste_app('glance-api'), default_port=9292)
    server.wait()

API 框架

API 框架

# file: /etc/glance/glance-api-paste.ini
[pipeline:glance-api-keystone]
pipeline = versionnegotiation authtoken context rootapp
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
delay_auth_decision = true
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app

关于 paste 模块的使用说明, 请参考 python paste.deploy 探索

流程如下:

  • 首先 WSGI 接收到用户请求, 将用户的请求信息交给 paste 模块处理
  • paste 模块根据配置文件的规则依次经过 versionnegotiation authtoken context 这几个过滤器
  • 最后交由 rootapp, 这是个类型为 app 的处理, rootapp 内部再根据用户提供的版本 信息(v1/v2) 交由 apiv1app 或者 apiv2app 处理, 最后把返回 HTTP Response
  • 真正的业务逻辑在 apiv1app/apiv2app 内部实现, 见下面的处理流程

处理流程

apiv2app 定义的工厂方法在 glance/api/v2/router:API.factory 中, API 类继承自 wsgi.Router, Router 类利用了 python-route 模块做 url 的选择处理, 具体的流程 见下(详细的分析请参考后面的系列): 处理流程 关于 route 模块的使用说明, 请参考 python Route 简单使用笔记

分类: OpenStack 标签:

Glance 源码分析(0) – 介绍

2014年10月23日 没有评论

这周我把 glance 的源代码通读了一遍(业务流程主要是 V2 版本), 感觉这个模块虽然是 OpenStack 里面相对较小的模块(vs Nova), 但是他的设计模式, 架构一点都不含糊, 我看完 glance 之后再去看其它项目, 发现 API 的等框架大部分地方都是一模一样. 也就是 说看完这个模块, 再去分析其它模块就省事很多了, 以下将我的分析拆分做一个记录:

分类: OpenStack 标签:

搭建一个最简单的 glance 服务

2014年10月14日 没有评论

OpenStack 的各个模块是高度独立的, glance, neutron, 并且可以给其他的程序使用, e.g. ovirt, 下面的文档描述使用最简单的方式搭建一个 glance 服务.(以 ubuntu 为例)

安装 glance

# apt-get install glance

That’s all!

上面搭建的 glance 是使用 sqlite 作为后端数据库, 没有使用认证的服务.

issue

在 icehouce 版本中, 由于配置文件解析和兼容的关系, 有一个 bug, 需要手动 在指定默认的 sqlite 数据库位置.

/etc/glance/glance-api.conf/etc/glance/glance-registry.conf[default] 下, 添加一个配置

connection = sqlite:////var/lib/glance/glance.sqlite

然后重新同步一下:

# glance-manage db_sync
# service glance-api restart
# service glance-registry restart

使用

由于使用 glanceclient 的话, 默认需要加上认证的信息, 所以只能通过 API 的方式 使用, 这里我使用 curl 作为测试工具:

获取 images 列表

# curl http://192.168.3.33:9292/v2/images | python -mjson.tool

下载 images

# curl -o test.img http://192.168.3.33:9292/v2/images/ce252e1a-131a-4ebd-a9b0-0cf462f066e6/file

上传 image

# curl -i -X POST -H 'Content-Type: application/octet-stream' -H 'x-image-meta-disk_forma2' \
  -H 'x-image-meta-container_format: bare' -H 'Transfer-Encoding: chunked' \
  -H 'User-Agent: python-glanceclient' -H 'x-image-meta-is_public: False' \
  -H 'x-image-meta-name: test' -H 'x-image-meta-size: 197120' \
  --data-binary @test.qcow2 http://192.168.3.33:9292/v1/images

删除 image

# curl -X DELETE http://192.168.3.33:9292/v2/images/d7aa01fc-5999-4720-8a05-325f7ffb9332

分类: OpenStack 标签:

在 RDO 中部署 Openstack Icehouse(1) – 使用 packstack 部署 openstack

2014年9月24日 没有评论

packstack 是利用 puppet 来简化安装部署 openstack 各个组件的工具, 利用它, 安装部署 OpenStack 非常方便.

首先安装该软件包

# yum install -y http://rdo.fedorapeople.org/openstack-icehouse/rdo-release-icehouse.rpm
# yum install -y openstack-packstack

然后生成应答文件

# packstack --gen-answer-file=my-answer.txt

编辑应答文件

# emacsclient my-answer.txt

修改以下几项

# 不安装 swift 服务
CONFIG_SWIFT_INSTALL=n
# 安装 heat 服务
CONFIG_HEAT_INSTALL=y 
CONFIG_COMPUTE_HOSTS=192.168.3.33
# 方便使用, 设置 web, admin 的密码为 admin
CONFIG_KEYSTONE_ADMIN_PW=admin
# 不要用文件模拟 LVM 作为 Cinder 的后端, 自己创建一个
CONFIG_CINDER_VOLUMES_CREATE=n
# 使用 gre, 不用 vxlan
CONFIG_NEUTRON_ML2_TYPE_DRIVERS=gre
CONFIG_NEUTRON_ML2_TENANT_NETWORK_TYPES=gre
CONFIG_NEUTRON_ML2_TUNNEL_ID_RANGES=1:1000
CONFIG_NEUTRON_OVS_TENANT_NETWORK_TYPE=gre
CONFIG_NEUTRON_OVS_BRIDGE_IFACES=br-ex:eth2
CONFIG_NEUTRON_OVS_TUNNEL_RANGES=1:1000
CONFIG_NEUTRON_OVS_TUNNEL_IF=eth1
# 我不需要 demo 用户
CONFIG_PROVISION_DEMO=n

创建 cinder 使用的 LVM 卷

# pvcreate /dev/vdb 
# vgcreate cinder-volumes /dev/vdb 
# vgs | grep cinder
  cinder-volumes   1   1   0 wz--n- 100.00g 99.00g

开始部署

# packstack --answer-file=my-answer.txt

安装完成后, 会看到以下信息:

Additional information:
 * Time synchronization installation was skipped. Please note that unsynchronized time on server instances might be problem for some OpenStack components.
 * File /root/keystonerc_admin has been created on OpenStack client host 192.168.3.31. To use the command line tools you need to source the file.
 * To access the OpenStack Dashboard browse to http://192.168.3.31/dashboard .
Please, find your login credentials stored in the keystonerc_admin in your home directory.
 * To use Nagios, browse to http://192.168.3.31/nagios username: nagiosadmin, password: 73e840f902334646
 * The installation log file is available at: /var/tmp/packstack/20140905-113333-bPPQjA/openstack-setup.log
 * The generated manifests are available at: /var/tmp/packstack/20140905-113333-bPPQjA/manifests

接下来, 就可以打开浏览器开启 openstack 之旅了

Table of Contents

bug issue

在安装过程中, 由于兼容性问题, 在 CentOS 7.0 中目前会出现一些问题, 主要参考以下文档解决

分类: OpenStack 标签:

在 RDO 中部署 Openstack Icehouse(0) – 环境描述

2014年9月7日 2 条评论

前面]] 说过, 用 Redhat的 packstack 工具部署 openstack 非常方便, 半年过去了, OpenStack 已经进入 J 版本的开发了, 并且 Icehouse 也已经很成熟了, 新的项目 和模块不断的被大家所接收, 这个系列试图描述在 RDO 上完整的部署一个可用的 openstack.

我的基本环境如下:

  • 基本系统: CentOS 7.0
  • packstack 版本: rdo-release-icehouse-4
  • OpenStack 版本: 2014.1.2(Icehouse)

各个节点的信息如下:

Hostname IPAddress Roles
rdo-control 192.168.3.31(mgt nic) controller node
rdo-network 192.168.3.32(mgt nic) 10.0.0.12(tun nic) eth2(external nic) network node
rdo-node1 192.168.3.33(mgt nic) 10.0.0.13(tun nic) computer node

安装基本系统

分别在三台机器上安装 CentOS 7.0 系统, 配置好最快的源, 配置主机和网络信息

# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.3.36 rdo-control rdo-control.test.com
192.168.3.37 rdo-network rdo-network.test.com
192.168.3.38 rdo-node1 rdo-node1.test.com

分别修改各个节点的主机名为相应的值:

# emacsclient /etc/hostname

网络设置

禁用 NetworkManager

# systemctl stop NetworkManager.service 
# systemctl disable NetworkManager.service
 
# systemctl enable network.service 
# systemctl start network.service

设置网络地址

controller node

# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
HWADDR=00:15:66:00:00:17
IPADDR=192.168.3.31
NETMASK=255.255.252.0
ONBOOT=yes
GATEWAY=192.168.0.1

network node

# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
HWADDR=00:15:66:00:00:18
IPADDR=192.168.3.32
NETMASK=255.255.252.0
ONBOOT=yes
GATEWAY=192.168.0.1
 
# cat ifcfg-eth1
DEVICE=eth1
HWADDR=00:15:65:00:00:19
IPADDR=10.0.0.5
NETMASK=255.255.255.0
ONBOOT=yes

compute node

# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
HWADDR=00:15:66:00:00:1A
IPADDR=192.168.3.33
NETMASK=255.255.252.0
ONBOOT=yes
GATEWAY=192.168.0.1
 
# cat ifcfg-eth1
DEVICE=eth1
HWADDR=00:15:65:00:00:1B
IPADDR=10.0.0.6
NETMASK=255.255.255.0
ONBOOT=yes

其它使用设置

在 controller 节点上把密钥复制到其它节点上, 方面后续的操作

# ssh-keygen
# ssh-copyid rdo-network
# ssh-copyid rdo-node1

分类: OpenStack 标签: