题记
在安装Openstack的时候,经常被一些乱七八糟的问题困扰,这里决定先把这个脚本理一下,后面在好好地自己写一个完善的安装脚本。
devstack项目地址:
http://devstack.org/
有一份英文版的源代码解读,不过如果对于Openstack没有过接触的人来说,可能看起来会有一些困难。
http://devstack.org/stack.sh.html
stack.sh 简介
stack.sh是一个openstack开发人员可以选择的一种安装脚本。
这个脚本的主要作用是安装并配置nova, glance, horizon, 以及keystone。
使用这个脚本,你可以设置一些配置选项,比如使用哪个git源,开启哪些服务,网络如何配置,以及各种密码的设置与管理。通过这个脚本,也可以灵活地在多台机器上运行,通过一个类似的资源进行一种共享式的配置。比如mysql, rabbitmq,从而可以组建一个多机环境下的云。
为了保证这个脚本足够简单,我们假设你是在一个Ubuntu 11.10 Oneiric的机器上执行。当然,这个脚本也可以在VM上运行,或是在物理机上运行。此外,我们把一些apt, pip安装包放到别的文件中去了,那么开始了解这个脚本,以及这些依赖的包。
如果你想进一步的学习或了解,可以去网站
http://devstack.org
Sanity Check
这个脚本首先来说是针对于Oneiric来写的,如果要在Ubuntu的其他版本上执行,当然也可以通过执行命令
,从而达到要求。但是我个人而言,不建议采用这种方式进来安装部署。在其他版本上的安装部署,后面再补上。
版本检查
下面这段代码就是主要是在检查一下OS的版本。
# DISTRO变量就是拿到OS的版本。在Ubuntu上执行,会得到发行版的名字。oneiric。
# 接下来的几句只是在进行检查是否是oneiric版本的Ubuntu
- DISTRO=$(lsb_release -c -s)
-
- if [[ ! ${DISTRO} =~ (oneiric) ]]; then
- echo "WARNING: this script has only been tested on oneiric"
- if [[ "$FORCE" != "yes" ]]; then
- echo "If you wish to run this script anyway run with FORCE=yes"
- exit 1
- fi
- fi
目录设定
stack.sh把很多需要安装的包与其他一些依赖都放到别的文件里面的,你可以在/devstack/files中找到这些文件。但是在脚本中,我们是通过FILES变量来引用到这些配置文件的位置。
#首先是拿到顶级目录所在位置。比如/root/devstack,那么$TOP_DIR="/root/devstack"
#生成FILES变量的值。再去检查一下这个目录是否存在。
- TOP_DIR=$(cd $(dirname "$0") && pwd)
-
- FILES=$TOP_DIR/files
- if [ ! -d $FILES ]; then
- echo "ERROR: missing devstack/files - did you grab more than just stack.sh?"
- exit 1
- fi
设定
这个脚本的可定制性体现在,一些环境变量可以放到别的文件中,或者是通过设定环境变量来进行处理。比如
- $export MYSQL_PASSWORD=anothersecret
- $./stack.sh
可以达到效果。当然,你也可以把这些语句放在同一行,比如
- $MYSQL_PASSWORD=simple ./stack.sh
也可以把这些设定放到$TOP_DIR/localrc文件中。
比如:
- $ cat $TOP_DIR/localrc
- .........
- MYSQL_PASSWORD=anothersecret
- MYSQL_USER=hellaroot
一般来说,我们都使用的是一些很敏感的设定,所以为了省事,你也可以运行
(一般而言,如果这样运行,脚本会在发现没有密码的时候,叫你输出密码,如果输入密码为空,那么会随机生成一个密码)。
环境变量
对于环境变量而言,一般我们都是放到stackrc文件中。这个文件是随着devstack一起发布的,并且主要是包含了需要使用的git源。如果你想要使用别的源,或者是别的分枝,那么你可以加入你自己的设定,并把这些设定写到localrc文件中。
一般而言localrc里面的设置会把stackrc文件中的变量给覆盖掉。这个是很有用的,特别是当你想使用别的源或者分枝的时候。你也可以更改掉一些已有设定,比如
- MYSQL_PASSWORD
- ADMIN_PASSWORD
否则devstack脚本会给你随机生成一个(这个随机生成的密码还有点长)。
#首先是从stackrc中引入环境变量
#$DEST变量是指的是安装目录,一般默认安装目录是/opt/stack
#下面是定义了一个函数,其实也就是使用apt-get命令。
- source ./stackrc
-
- DEST=${DEST:-/opt/stack}
-
- function apt_get() {
- [[ "$OFFLINE" = "True" ]] && return
- local sudo="sudo"
- [ "$(id -u)" = "0" ] && sudo="env"
- $sudo DEBIAN_FRONTEND=noninteractive apt-get \
- --option "Dpkg::Options::=--force-confold" --assume-yes "$@"
- }
这里检查是否已经有stack.sh程序在运行。
- if screen -ls | egrep -q "[0-9].stack"; then
- echo "You are already running a stack.sh session."
- echo "To rejoin this session type 'screen -x stack'."
- echo "To destroy this session, kill the running screen."
- exit 1
- fi
因为Openstack在设计的时候,是用的一般用户来运行的,主要原因是因为dashboard在运行的时候底层使用的是apache2的服务器,而apache2不可以在root权限下执行。
如果在运行脚本的时候是使用的是root权限,那么会自动新建一个stack用户来执行操作。并且这个用户会给足够的权限来进行很多操作
- if [[ $EUID -eq 0 ]]; then
- ROOTSLEEP=${ROOTSLEEP:-10}
- echo "You are running this script as root."
- echo "In $ROOTSLEEP seconds, we will create a user 'stack' and run as that user"
- sleep $ROOTSLEEP
-
- # 因为这个脚本接下来是按照一个普通用户来执行的,那么需要给这个普通用户以sudo的权限。
- #
- dpkg -l sudo || apt_get update && apt_get install sudo
-
- if ! getent passwd stack >/dev/null; then
- echo "Creating a user called stack"
- useradd -U -G sudo -s /bin/bash -d $DEST -m stack
- fi
-
- echo "Giving stack user passwordless sudo priviledges"
- # some uec images sudoers does not have a '#includedir'. add one.
- grep -q "^#includedir.*/etc/sudoers.d" /etc/sudoers ||
- echo "#includedir /etc/sudoers.d" >> /etc/sudoers
- ( umask 226 && echo "stack ALL=(ALL) NOPASSWD:ALL" \
- > /etc/sudoers.d/50_stack_sh )
-
- echo "Copying files to stack user"
- STACK_DIR="$DEST/${PWD##*/}"
- cp -r -f -T "$PWD" "$STACK_DIR" #这里在拷文件,额外的拷文件的动作也可以加在这里。
- chown -R stack "$STACK_DIR"
- if [[ "$SHELL_AFTER_RUN" != "no" ]]; then
- exec su -c "set -e; cd $STACK_DIR; bash stack.sh; bash" stack
- else
- exec su -c "set -e; cd $STACK_DIR; bash stack.sh" stack
- fi
- exit 1
- else
- # Our user needs passwordless priviledges for certain commands which nova
- # uses internally.
- # Natty uec images sudoers does not have a '#includedir'. add one.
- sudo grep -q "^#includedir.*/etc/sudoers.d" /etc/sudoers ||
- echo "#includedir /etc/sudoers.d" | sudo tee -a /etc/sudoers
- TEMPFILE=`mktemp`
- cat $FILES/sudo/nova > $TEMPFILE
- sed -e "s,%USER%,$USER,g" -i $TEMPFILE
- chmod 0440 $TEMPFILE
- sudo chown root:root $TEMPFILE
- sudo mv $TEMPFILE /etc/sudoers.d/stack_sh_nova
- fi
接下来这个就是设置是否是用离线的方式来进行安装,或者是用上网的方式来进行安装。
- function trueorfalse() {
- local default=$1
- local testval=$2
-
- [[ -z "$testval" ]] && { echo "$default"; return; }
- [[ "0 no false False FALSE" =~ "$testval" ]] && { echo "False"; return; }
- [[ "1 yes true True TRUE" =~ "$testval" ]] && { echo "True"; return; }
- echo "$default"
- }
- OFFLINE=`trueorfalse False $OFFLINE`
一般来说,安装方式都是在线的方式来安装的。如果设置的值是True,那么就是使用离线的方式来进行安装。
配置服务
接下来是设置一些环境变量:这些环境变量主要都是为了安装某个部件而设置的。
- NOVA_DIR=$DEST/nova
- HORIZON_DIR=$DEST/horizon
- GLANCE_DIR=$DEST/glance
- KEYSTONE_DIR=$DEST/keystone
- NOVACLIENT_DIR=$DEST/python-novaclient
- OPENSTACKX_DIR=$DEST/openstackx
- NOVNC_DIR=$DEST/noVNC
- SWIFT_DIR=$DEST/swift
- SWIFT_KEYSTONE_DIR=$DEST/swift-keystone2
- QUANTUM_DIR=$DEST/quantum
下面的设置是为了Quantum而进行设置。
- # Default Quantum Plugin #看样子这个应该是一个网络服务
- Q_PLUGIN=${Q_PLUGIN:-openvswitch}
- # Default Quantum Port #这里设置端口
- Q_PORT=${Q_PORT:-9696}
- # Default Quantum Host #这里设置主机
- Q_HOST=${Q_HOST:-localhost}
接下来设置哪些服务要开启。注意这里用到的都是缩写。
- ENABLED_SERVICES=${ENABLED_SERVICES:-g-api,g-reg,key,n-api,n-cpu,n-net,n-sch,n-vnc,horizon,mysql,rabbit,openstackx}
下面是设置volume服务
- # Name of the lvm volume group to use/create for iscsi volumes
- VOLUME_GROUP=${VOLUME_GROUP:-nova-volumes}
- VOLUME_NAME_PREFIX=${VOLUME_NAME_PREFIX:-volume-}
- INSTANCE_NAME_PREFIX=${INSTANCE_NAME_PREFIX:-instance-}
接下来是设置需要使用到的底层的服务
- VIRT_DRIVER=${VIRT_DRIVER:-libvirt}
- LIBVIRT_TYPE=${LIBVIRT_TYPE:-kvm}
一般而言我们都是使用kvm+libvirt。如果kvm没有驱动起来,我们会使用qemu。
设置要使用到的Scheduler
- SCHEDULER=${SCHEDULER:-nova.scheduler.simple.SimpleScheduler}
接下来是拿到主机IP地址。
- if [ ! -n "$HOST_IP" ]; then
- HOST_IP=`LC_ALL=C /sbin/ifconfig eth0 | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'`
- if [ "$HOST_IP" = "" ]; then
- echo "Could not determine host ip address."
- echo "If this is not your first run of stack.sh, it is "
- echo "possible that nova moved your eth0 ip address to the FLAT_NETWORK_BRIDGE."
- echo "Please specify your HOST_IP in your localrc."
- exit 1
- fi
- fi
这里是设置一下是否使用系统日志:
- SYSLOG=`trueorfalse False $SYSLOG`
- SYSLOG_HOST=${SYSLOG_HOST:-$HOST_IP}
- SYSLOG_PORT=${SYSLOG_PORT:-516}
服务超时设定
- SERVICE_TIMEOUT=${SERVICE_TIMEOUT:-60}
接下这个函数就是为了读取密码而写的。
- function read_password {
- set +o xtrace
- var=$1; msg=$2
- pw=${!var}
-
- localrc=$TOP_DIR/localrc
-
- # If the password is not defined yet, proceed to prompt user for a password.
- if [ ! $pw ]; then
- # If there is no localrc file, create one
- if [ ! -e $localrc ]; then
- touch $localrc
- fi
-
- # Presumably if we got this far it can only be that our localrc is missing
- # the required password. Prompt user for a password and write to localrc.
- echo ''
- echo '################################################################################'
- echo $msg
- echo '################################################################################'
- echo "This value will be written to your localrc file so you don't have to enter it again."
- echo "It is probably best to avoid spaces and weird characters."
- echo "If you leave this blank, a random default value will be used."
- echo "Enter a password now:"
- read -e $var
- pw=${!var}
- if [ ! $pw ]; then
- pw=`openssl rand -hex 10`
- fi
- eval "$var=$pw"
- echo "$var=$pw" >> $localrc
- fi
- set -o xtrace
- }
你如果想设置成为一个固定的密码,那么这里可以改为如下:
- # Presumably if we got this far it can only be that our localrc is missing
- # the required password. Prompt user for a password and write to localrc.
- echo "Set password to nova:"
- pw="nova"
- eval "$var=$pw"
- echo "$var=$pw" >> $localrc
为了简单起见,后面会一直使用nova做为密码。(自己安装着玩的时候这么干比较好,如果正式用的话,还是用一个靠谱的密码吧。)
设置网络:这些设定应该最后都会写到/etc/nova/nova.conf文件中的。不过这里好像是放在安装目录中。
- PUBLIC_INTERFACE=${PUBLIC_INTERFACE:-eth0}
- FIXED_RANGE=${FIXED_RANGE:-10.0.0.0/24}
- FIXED_NETWORK_SIZE=${FIXED_NETWORK_SIZE:-256}
- FLOATING_RANGE=${FLOATING_RANGE:-172.24.4.224/28}
- NET_MAN=${NET_MAN:-FlatDHCPManager}
- EC2_DMZ_HOST=${EC2_DMZ_HOST:-$HOST_IP}
- FLAT_NETWORK_BRIDGE=${FLAT_NETWORK_BRIDGE:-br100}
- VLAN_INTERFACE=${VLAN_INTERFACE:-$PUBLIC_INTERFACE}
下面是设置是否使用多节点模式,如果使用多节点模式,那么每个计算节点都会与网络节点都是一体的,访问相应的VM的时候,直接访问相应的物理机则可。
这种模式可以解除SPOF和带宽的制约。
- MULTI_HOST=${MULTI_HOST:-False}
下面一段话比较长,不过也可以看一下:
如果你是在多台物理机上使用FlatDHCP模式,设置"FLAT_INTERFACE"变量,并且要保证这个interface并没有一个IP。否则你可能会破坏一些东西。
"*DHCP Warning" 这种情况,如果你使用flat interface设备使用DHCP,发生下面这种情况的时候,可能会让你呆住,
网络从flat interface上移到flat network bridge。这种情况完全有可能发生。结果是当你创建第一台虚拟机的时候。这种情况会导致你的虚拟机出问题,比如连接不上,或者登陆失败。
如果你只是装着单机版的来玩玩,那么你可以使用FLAT_NETWORK_BRIDGE。
- FLAT_INTERFACE=${FLAT_INTERFACE:-eth0}
quantum网络
你需要确保quantum是在Enabled_services中是开启的。假如network manager设置成为QuantumManager。
假如你想在这台机器上运行Quantum,那么最好在ENABLED_SERVICES中要找到q-svc字段。
如果你想在quantum中使用openswitch插件,那么把 Q_PLUGIN 的值设置为openvswitch。并且需要保证q-agt服务也是在开启的服务之列。
如果你设置了Quantum网络,那么NET_MAN变量的值就被忽略了。
MYSQL 和 RabbitMQ
在这里,我们配置的目标是让Nova, Horizon, Glance, 与Keystone都使用MySQL做为其数据库。这里他们主要是使用一个单一的服务,但是每个服务都是有他们自己的表数据库与表单。
一般而言,这里的脚本会安装和配置MySQL。如果你想用一个已经有的服务,那么你需要传递/user/password/host参数。也同时需要传递MYSQL_PASSWORD参数到每个节点(当你使用的单台机器的时候,这一步就省了吧)。一般而言我都是采用一种最简单的方式,也就是一台服务器,也都使用这个服务器上的MySQL数据库。
下面的三行代码分别是设置MySQL的机器地址与用户名与密码。在这里是需要手动输入密码。
- MYSQL_HOST=${MYSQL_HOST:-localhost}
- MYSQL_USER=${MYSQL_USER:-root}
- read_password MYSQL_PASSWORD "ENTER A PASSWORD TO USE FOR MYSQL."
下面再给出一个链接,这个链接主要是用于连接MYSQL数据库。一般不需要去加上所需要连接的DataBase,因为其他几个服务都是需要通过这个链接去连MySQL。但是每个服务连接的数据库是不一样的。所以如果是用同一样的链接+数据库名,肯定会出错。一句话,保持下面这个不变就可以了。
- BASE_SQL_CONN=${BASE_SQL_CONN:-mysql:
设置RabbitMQ的密码
- RABBIT_HOST=${RABBIT_HOST:-localhost}
- read_password RABBIT_PASSWORD "ENTER A PASSWORD TO USE FOR RABBIT."
接下来指出Glance的主机与端口,值得注意的是这里需要给出端口。这个端口按照原文好像是需要指定的,特定的这么个端口(总之保持不变就是了).。
- GLANCE_HOSTPORT=${GLANCE_HOSTPORT:-$HOST_IP:9292}
Swift设置
需要完善的功能:
TODO:实现对于glance的支持。
TODO: 加入对于不同存储位置的日志功能。
一般来说,swift驱动的位置与存储对象是放在swift源码的内部文件夹中。当然也可以自已指定一个目录。
- SWIFT_DATA_LOCATION=${SWIFT_DATA_LOCATION:-${SWIFT_DIR}/data}
同样的道里,swift的配置文件也是放在源码内部的。当然也可以根据你自己的情况做出调整。
- SWIFT_CONFIG_LOCATION=${SWIFT_CONFIG_LOCATION:-${SWIFT_DIR}/config}
接下来,devstack需要建立一个loop-back的磁盘,这个磁盘会被格式化为XFS格式,并且用来存储swift数据。一般来说,这个磁盘的大小是1G。变量SWIFT_LOOPBACK_DISK_SIZE指出了这个磁盘的大小。当然你也可以根据自己的情况来调整大小。
- SWIFT_LOOPBACK_DISK_SIZE=${SWIFT_LOOPBACK_DISK_SIZE:-1000000}
接下来是swift需要读入一个密码。这一段比较难以理解(主查涉及到swift内部的一些原理与实现)。
- # The ring uses a configurable number of bits from a path’s MD5 hash as
- # a partition index that designates a device. The number of bits kept
- # from the hash is known as the partition power, and 2 to the partition
- # power indicates the partition count. Partitioning the full MD5 hash
- # ring allows other parts of the cluster to work in batches of items at
- # once which ends up either more efficient or at least less complex than
- # working with each item separately or the entire cluster all at once.
- # By default we define 9 for the partition count (which mean 512).
- SWIFT_PARTITION_POWER_SIZE=${SWIFT_PARTITION_POWER_SIZE:-9}
-
- # We only ask for Swift Hash if we have enabled swift service.
- if [[ "$ENABLED_SERVICES" =~ "swift" ]]; then
- # SWIFT_HASH is a random unique string for a swift cluster that
- # can never change.
- read_password SWIFT_HASH "ENTER A RANDOM SWIFT HASH."
- fi
Keystone
服务口令:Openstack的组件需要有一个管理员口令来认证用户的口令。
下面则是读入一个密码:
- read_password SERVICE_TOKEN "ENTER A SERVICE_TOKEN TO USE FOR THE SERVICE ADMIN TOKEN."
同样也需要为Horizon输入一个密码。
- read_password ADMIN_PASSWORD "ENTER A PASSWORD TO USE FOR HORIZON AND KEYSTONE (20 CHARS OR LESS)."
接下来是设置日志
- LOGFILE=${LOGFILE:-"$PWD/stack.sh.$.log"}
- (
- # So that errors don't compound we exit on any errors so you see only the
- # first error that occurred.
- trap failed ERR
- failed() {
- local r=$?
- set +o xtrace
- [ -n "$LOGFILE" ] && echo "${0##*/} failed: full log in $LOGFILE"
- exit $r
- }
接下来的这个命令比较有用,就是打出每个命令,以及这个命令的输出,主要是用于查看在哪里出错了。以便及时查错。
创建目标目录,并且要保证stack用户对于这个目录是可写的。
- sudo mkdir -p $DEST
- if [ ! -w $DEST ]; then
- sudo chown `whoami` $DEST
- fi
前面讲了这么多,其实都是在讲怎么配置。接下来才是要进行人工操作阶段。
安装依赖包
Openstack用了很多其他的工程。所以需要安装其他工程。
注意的是:
我们只安装对于我们的服务来说是必须的包。
脚本学习---得到操作系统发行版的方法
方法一
- DISTRO=$(lsb_release -c -s)
可以用来得到操作系统的发行版。
缺点,
虽然这种方法可以在ubuntu上得到类似于oneiric的字样(如果你的发行版不同,那么得到的也肯定不一样)。
至于其他版本,则得到的结果就有点奇怪了。比如我在suse上运行,是得不到相应的结果的。
方法二
其实操作系统发行版的信息是写在/etc/issue 和/etc/issue.net文件中。为了方便我们用如下命令去查看吧。
在ubuntu下的运行结果如下:
- $ cat /etc/issue*
- Ubuntu 11.10 \n \l
-
- Ubuntu 11.10
在SUSE下面运行有如下结果:
- $ cat /etc/issue*
- Welcome to SUSE Linux Enterprise Server 11 SP1 (x86_64) - Kernel \r (\l).
-
-
- Welcome to SUSE Linux Enterprise Server 11 SP1 (x86_64) - Kernel %r (%t).
为了简单起见,我们需要把这些字符串转化成为小写,再进行判断。因为有时候有的发行版在说明的时候会写SUSE, 而Ubuntu的写的是Ubuntu,并不是全部都是大写。
我们用如下脚本来进行测试是否是suse系统
- "color:#3333FF;">if [[ `cat $DISTRO | grep suse | wc -l` -eq 0 ]]; then
- echo "Not suse"
- exit 1
- fi
脚本学习----如何得到脚本运行时所在目录。
当在问这个问题的时候,很有可能会回答说是pwd,在C语言里用的是getcwd()函数。实际上,pwd这个命令返回值是运行命令时所在路径,而不是脚本所在目录。
如果对于这个问题不是很了解。那么换个问题就是,一个脚本,如何知道自己所在的目录?这个脚本可以被放在很多位置。
方法一
第一种方法就是这个stack.sh脚本中所提到的,
- TOP_DIR=$(cd $(dirname "$0") && pwd)
一开始看时,可能不太明白其含义,现解释如下:
- $0 这个只能是在脚本里面使用,在命令行里面只会输出-bash。$0表示的意思是运行脚本的全路径。
-
- dirname这个命令就是截取全路径中的目录部分比如
- $ dirname /root/stack/nova.sh
- /root/stack
-
- 实际上,在脚本中,使用dirname $0可能就已经够用了。之所以再用cd && pwd我觉得主要的原因是怕$0给出的不是全路径。
方法二
来个简洁版的,
从而可以确定脚本所在目录。
脚本学习---如何取得全路径中的路径
方法一
dirname $par
方法二
pkg_dir=${par%/*}