blog

DeNAのエンジニアが考えていることや、担当しているサービスについて情報発信しています

2017.08.15 技術記事

DeNAにおけるOpenStack Upgrade

by kengo.sakai

#openstack

はじめに

こんにちは。IT基盤部でOpenStackの運用をしています酒井です。
弊社では先日社内のOpenStackのアップグレードを行いました。今回は弊社で実施したアップグレード手順についてできるだけ詳しく紹介させていただきます。

なぜアップグレードするのか

OpenStackのリリースサイクルは こちら にあるように約半年ごとに新しいバージョンがリリースされ新機能が追加されます。弊社ではkiloとlibetyを利用していましたが、mitakaで導入された新しい機能を使いたいため、ひとまず両環境をmitakaにアップグレードすることをターゲットとしました。また、弊社ではSDNとしてBig Cloud Fabric(以下BCF)を使用していますが、BCFのサポートするOpenStackバージョンが決まっているため、OpenStackをアップグレードしないとBCFをアップグレードできない、という事情もありました。

OpenStackの構成

弊社のOpenStackの構成としては以下の通りです。

OSUbuntu 14.04
OpenStack Versionkilo/liberty
OpenStack ComponentKeystone, Glance, Nova, Neutron, Cinder, Ironic
HypervisorKVM
Cinder BackendCeph
Neutron mechanism_driversopenvswitch, bsn_ml2, networking_bigswitch_l3_pe
(l3-agentは無し)

OpenStack環境が2環境あり、それぞれkiloとlibertyを利用していました。kiloからmitakaへのアップグレードは一度には行わず、kiloからliberty、libertyからmitakaと2回に分けてアップグレードしました。これは当時使っていたBCFのバージョンではkiloとlibertyのみをサポートしており、mitakaをサポートしているバージョンのBCFではkiloをサポートしていなかったため、このような段階的なアップグレードをする必要があったためです。

アップグレード手順

基本的な手順としては以下のようになります。

  1. 事前準備
  2. 新バージョンのController Nodeを新規で構築する。(DB, rabbitmqはController Node上で動作する構成)
  3. メンテナンス期間: 各コンポーネント毎に以下の作業を行う
  4. 旧バージョンのController Nodeのサービス停止
  5. DBを新バージョンに対応させるためマイグレーション
  6. 新バージョンのController Nodeのサービス起動
  7. 動作確認
  8. DNS更新してエンドポイント切り替え
  9. Compute Node上で動作するサービスがあれば(弊社のケースではNovaとNeutron)新バージョンにアップグレード

各コンポーネントのAPIの停止を伴うため、社内の関係者と調整しメンテナンス期間を設けてアップグレード作業を行いました。コンポーネントのアップグレードはKeystone, Glance, Nova, Neutron, Cinder, Ironicの順に行いました。各コンポーネントごとの詳細な手順を以下に説明します。なお、kiloからlibertyへのアップグレードとlibertyからmitakaへのアップグレードでは手順はほぼ同様でしたが、一部異なる箇所がありましたのでそこは個別に説明します。

Keystone

旧バージョンのController Nodeのkeystoneを停止します。

service apache2 stop

次にkeystone DBを新バージョンに対応させるためスキーマのマイグレーションします。

mysqldump -uroot -h$OLD_DB_SERVER -p --opt --add-drop-database --single-transaction --master-data=2 keystone > keystone-db-backup.sql
mysql -uroot -h$NEW_DB_SERVER -p -f keystone < keystone-db-backup.sql
sudo su -s /bin/sh -c "keystone-manage db_sync" keystone

新バージョンのController Nodeでサービス再開します

service apache2 start

以下のような動作確認をします。

# 35357ポートの確認
TOKEN=$(openstack --os-url http://$MY_IP:35357/v2.0 token issue -f json | jq -r .id)
openstack --os-url http://$MY_IP:35357/v2.0 --os-token $TOKEN user list
## 5000ポートの確認
openstack --os-url http://$MY_IP:5000/v2.0 token issue

問題なければDNSを更新してendpointを新バージョンに切り替えます。

Glance

旧バージョンのController Nodeのglanceを停止します。

stop glance-registry
stop glance-api

次にglance DBを新バージョンに対応させるためスキーマのマイグレーションします。

mysqldump -uroot -h$OLD_DB_SERVER -p --opt --add-drop-database --single-transaction --master-data=2 glance > glance-db-backup.sql
mysql -uroot -h$NEW_DB_SERVER -p -f glance < glance-db-backup.sql
sudo su -s /bin/sh -c "glance-manage db_sync" glance

旧バージョンのController Node上に保存されているimageファイルを新バージョンのController Nodeにコピーします。

rsync -av $OLD_CONTROLLER_NODE:/var/lib/glance/images/
/var/lib/glance/images/

新バージョンのController Nodeでサービス再開します

start glance-api
start glance-registry

以下のような動作確認をします。

MY_IP=`hostname -i`
## glance image-listの確認
glance --os-image-url http://$MY_IP:9292/ image-list
## glance image-createの確認
echo dummy | glance --os-image-url http://$MY_IP:9292/ image-create --disk-format raw --container-format bare --name dummy_image
## glance image-downloadの確認
glance --os-image-url http://$MY_IP:9292/ image-download <新規イメージのID> > dummy_image
cat dummy_image # dummyと表示されればOK
## glance image-deleteの確認
glance --os-image-url http://$MY_IP:9292/ image-delete <新規イメージのID>

問題なければDNSを更新してendpointを新バージョンに切り替えます。

Nova(Controller Node)

旧バージョンのController Nodeのnovaを停止します。

stop nova-cert
stop nova-consoleauth
stop nova-novncproxy
stop nova-conductor
stop nova-scheduler
stop nova-compute
stop nova-api

nova DBを新バージョンに対応させるためスキーマのマイグレーションします。

mysqldump -uroot -h$OLD_DB_SERVER -p --opt --add-drop-database --single-transaction --master-data=2 nova > nova-db-backup.sql
mysql -uroot -h$NEW_DB_SERVER -p -f nova < nova-db-backup.sql
sudo su -s /bin/sh -c "nova-manage db sync" nova
## mitakaへのアップグレードの場合は nova-api DBのマイグレーションも必要です。
sudo su -s /bin/sh -c "nova-manage api_db sync" nova

新バージョンのController Nodeでサービス再開します

start nova-api
start nova-compute
start nova-scheduler
start nova-conductor
start nova-novncproxy
start nova-consoleauth
start nova-cert

以下のような動作確認をします。

MY_IP=`hostname -i`
PROJECT_ID=$(openstack project show $OS_PROJECT_NAME -f json | jq -r .id)
TOKEN=$(openstack token issue -f json | jq -r .id)
curl -i http://$MY_IP:8774/v2/$PROJECT_ID/servers/detail  -H "Accept: application/json" -H "X-Auth-Token: $TOKEN"
curl -i http://$MY_IP:8774/v2/$PROJECT_ID/images/detail  -H "Accept: application/json" -H "X-Auth-Token: $TOKEN"

問題なければDNSを更新してendpointを新バージョンに切り替えます。

Nova(Compute Node)

最初にneutron-ovs-cleanupが起動していることを確認します。neutron-ovs-cleanupが停止しているとnova-compute再起動時にneutron-ovs-cleanupが実行され、インスタンスの通信が途切れてしまうためです。

status neutron-ovs-cleanup

新バージョンのaptリポジトリを追加します。kiloからlibertyへのアップグレードの場合は以下のようになります。

OLD_OS_VERSION=kilo
NEW_OS_VERSION=liberty
echo "deb http://ubuntu-cloud.archive.canonical.com/ubuntu trusty-updates/$NEW_OS_VERSION main" > /etc/apt/sources.list.d/cloudarchive-$NEW_OS_VERSION.list
mv /etc/apt/sources.list.d/cloudarchive-$OLD_OS_VERSION.list /tmp
apt-get update

まずnova, neutron, openvswitch以外のパッケージをアップグレードします。

apt-mark hold nova-compute
apt-mark hold openvswitch-common openvswitch-switch
apt-mark hold neutron-plugin-openvswitch-agent neutron-plugin-ml2
apt-get dist-upgrade

nova.confを必要に応じて修正した後、novaをアップグレードします。

apt-mark unhold nova-compute
apt-get dist-upgrade

当該Compute Nodeのnova-computeがupしていることを確認します。

nova service-list

Neutron(Controller Node)

旧バージョンのController NodeのNeutronを停止します。neutron-dhcp-agentを止める前にstate downにすることによりdnsmasqプロセスを終了させることができます。またその後state upにすることにより、新バージョンのneutron-dhcp-agentが各ネットワークに自動的にbindされるようになります。

neutron agent-update --admin-state-down $DHCP_AGENT_ID
## dnsmasqプロセスがいないことを確認した後neutron-dhcp-agentを停止する
stop neutron-dhcp-agent
neutron agent-update --admin-state-up $DHCP_AGENT_ID
stop neutron-metadata-agent
stop neutron-plugin-openvswitch-agent
stop neutron-server

Neutron DBを新バージョンに対応させるためスキーマのマイグレーションします。

mysqldump -uroot -h$OLD_DB_SERVER -p --opt --add-drop-database --single-transaction --master-data=2 neutron > neutron-db-backup.sql
mysql -uroot -h$OLD_DB_SERVER -p -f neutron < neutron-db-backup.sql
sudo su -s /bin/sh -c "neutron-db-manage --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugins/ml2/ml2_conf.ini upgrade head" neutron

新バージョンのController Nodeでサービス再開します

start neutron-server
start neutron-plugin-openvswitch-agent
start neutron-metadata-agent
start neutron-dhcp-agent

以下のような動作確認をします。

TOKEN=$(openstack token issue -f json | jq -r .id)
curl -i http://$MY_IP:9696/v2.0/networks.json -H "Accept: application/json" -H "X-Auth-Token: $TOKEN"

問題なければDNSを更新してendpointを新バージョンに切り替えます。

Neutron(Compute Node)

openvswitch、Neutronの順にアップグレードを行います。 まずopenvswitchのアップグレードについてですが、kiloからlibertyへのアップグレードの場合、qvo Portのother_configに設定を追加する必要があります。以下のようにkiloではother_configが空になっているため、libertyへアップグレード後neutron-openvswitch-agentを再起動するとtagの情報が失われtagの再割り当てが行われます。その結果インスタンスの通信が数秒程度途絶えることになります。

# kiloではother_configは空になっている
ovs-vsctl --columns=name,other_config list Port qvoe97fb5a8-67
name                : "qvoe97fb5a8-67"
other_config        : {}
## libertyでは各種コンフィグが保存されている
ovs-vsctl --columns=name,other_config list Port qvoe97fb5a8-67
name                : "qvoe97fb5a8-67"
other_config        : {net_uuid="83b7bfb1-0b2f-406a-8725-fdaa7daf563f", network_type=vlan, physical_network="physnet1", segmentation_id="113", tag="5"}

これを避けるため、アップグレード前に全Portに対し以下のようなコマンドを実行し手動でother_configを設定しました。

ovs-vsctl set Port qvoe97fb5a8-67 other_config='{net_uuid="83b7bfb1-0b2f-406a-8725-fdaa7daf563f", network_type=vlan, physical_network="physnet1", segmentation_id="113", tag="5"}'

またkiloからlibertyの場合、conntrack zoneの対応もする必要があります。libertyではportごとに個別のconntrack zoneを使ってコネクションを管理するようになりました。kiloではconntrack zoneの指定はされておらず全てのportがデフォルトのzone 0で管理されていました。そのままlibertyにアップグレードするとzoneが新規に割り当てられてしまい( ソースコードはこちら )、割り当てられたzoneにはコネクションがないためそのコネクションのパケットがドロップしてしまうということがわかりました。これを回避するため、neutron-openvswitch-agentがzone 0を使い続けるよう、まず以下のようなルールを追加設定しました。

iptables -t raw -A neutron-openvswi-PREROUTING -m physdev --physdev-in $dev -j CT --zone 0

また、既存のiptables-saveコマンドのwrapperを以下のように作成しました。

cat <<EOF | sudo tee /usr/local/sbin/iptables-save
#!/bin/sh
/sbin/iptables-save "\$@" | sed 's/ -j CT$/ -j CT --zone 0/'
EOF
chmod +x /usr/local/sbin/iptables-save

vi /etc/neutron/rootwrap.conf
## /usr/local/sbin/を優先するよう以下のように修正
exec_dirs=/usr/local/sbin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin

この二つの対応により各portがzone 0を使い続けるようになります。

以上で準備ができましたのでopenvswitchのアップグレードを行います。openvswitch-switchパッケージインストール時にopenvswitch-switchサービスが再起動されるのですが、そのタイミングでネットワークが不通になることがありました。それを避けるためにここではopenvswitch-switchサービスを再起動しないようにしています。

# ダミーの何もしないinvoke-rc.dで本物のinvoke-rc.dを隠すことでopenvswitch-switchを再起動しないようにする
ln -s `which true` /usr/local/sbin/invoke-rc.d
apt-mark unhold openvswitch-common openvswitch-switch
apt-get dist-upgrade -s
apt-get dist-upgrade
## invole-rc.dを元に戻す
rm /usr/local/sbin/invoke-rc.d

パッケージのアップグレードが終わった後で手動でモジュール再読込を実施します。弊社の環境ではこのタイミングでインスタンスの通信が0.5秒程度途切れました。

/usr/share/openvswitch/scripts/ovs-ctl force-reload-kmod

次にNeutronのアップグレードです。まずアップグレードの前にneutron-openvswitch-agentを停止します。

stop neutron-plugin-openvswitch-agent

kiloからlibertyへのアップグレード時にはNeutronのアップグレードの前にOVSブリッジにfail_mode: secureを設定する必要があります。Neutronのバージョン7.2.0からOVSブリッジにfail_mode: secureが設定されるようになりました。fail_mode: secureでない状態でneutron-openvswitch-agentを起動するとneutron-openvswitch-agentによりOVSブリッジにfail_mode: secureが設定されるのですが、この処理にはフロー情報のクリアが伴いインスタンスのネットワーク断が発生してしまいます。 これを避けるため、事前にfail_mode: secureの設定、フロー情報のリストアを行います。

ovs-ofctl dump-flows br-ex | grep -v NXST_FLOW > br-ex.flow; ovs-vsctl set-fail-mode br-ex secure; ovs-ofctl add-flows br-ex - < br-ex.flow

Neutronのコンフィグを必要に応じて修正した後、neutronをアップグレードします。

apt-mark unhold neutron-plugin-openvswitch-agent neutron-plugin-ml2
apt-get dist-upgrade

当該Compute Nodeのneutron-plugin-openvswitch-agentがupしていることを確認します。

neutron agent-list

Cinder

旧バージョンのController Nodeのcinderを停止します。

stop cinder-volume
stop cinder-scheduler
stop cinder-api

次にcinder DBを新バージョンに対応させるためスキーマのマイグレーションします。

mysqldump -uroot -h$OLD_DB_SERVER -p --opt --add-drop-database --single-transaction --master-data=2 cinder > cinder-db-backup.sql
mysql -uroot -h$NEW_DB_SERVER -p -f cinder < cinder-db-backup.sql
sudo su -s /bin/sh -c "cinder-manage db sync" cinder

弊社の場合cinder DBのvolumesテーブルのhostカラムを更新する必要がありました。このカラムには以下のようにcinder-volumeを動かしていた旧Controller Nodeのホスト名が含まれていました。Controller Nodeのホスト名が変わる度にこのカラムを更新するのは手間なので、弊社ではcinder.confにhostパラメタとして別名を定義し、その値をこのカラムに使用するようDBを更新しました。

mysql> select distinct(host) from volumes;
+-------------------------------+
| host                          |
+-------------------------------+
| CONTROLLER_HOSTNAME@ceph#CEPH |
+-------------------------------+

新バージョンのController Nodeでサービス再開します

start cinder-api
start cinder-scheduler
start cinder-volume

以下のような動作確認をします。

MY_IP=`hostname -i`
PROJECT_ID=$(openstack project show $OS_PROJECT_NAME -f json | jq -r .id)
TOKEN=$(openstack token issue -f json | jq -r .id)
curl -i http://$MY_IP:8776/v2/$PROJECT_ID/volumes/detail?all_tenants=1 -H "Accept: application/json" -H "X-Auth-Token: $TOKEN"

問題なければDNSを更新してendpointを新バージョンに切り替えます。

Ironic

旧バージョンのController Nodeのironicを停止します。

stop ironic-conductor
stop ironic-api

次にironic DBを新バージョンに対応させるためスキーマのマイグレーションします。

mysqldump -uroot -h$OLD_DB_SERVER -p --opt --add-drop-database --single-transaction --master-data=2 ironic > ironic-db-backup.sql
mysql -uroot -h$NEW_DB_SERVER -p -f ironic < ironic-db-backup.sql
sudo su -s /bin/sh -c "ironic-dbsync --config-file /etc/ironic/ironic.conf upgrade" ironic

弊社の場合nova DBのinstancesテーブルのhost/launched_onカラムを更新する必要がありました。これらには旧バージョンのController Nodeのホスト名が保存されていたのですが、以下のようにこれらの値を新バージョンのController Nodeのホスト名に更新しました。なお、cinder DBの場合と同様に別名を使うということも検討したのですが、BCF環境ではホスト名を使ったほうが安全にアップグレードが行えるということがわかり(詳細は割愛しますが)、ホスト名を使い続けることとしました。

mysql> update instances set host = '$NEW_CONTROLLER_NODE' where host = '$OLD_CONTROLLER_NODE';
mysql> update instances set launched_on = '$NEW_CONTROLLER_NODE' where launched_on = '$OLD_CONTROLLER_NODE';

旧バージョンのController Node上に保存されているtftp関連のファイルやimageファイルを新バージョンのController Nodeにコピーします。

mkdir /tftpboot
chown ironic.ironic /tftpboot
rsync -av $OLD_CONTROLLER_NODE:/tftpboot/ /tftpboot/
rsync -av $OLD_CONTROLLER_NODE:/var/lib/ironic/images/ /var/lib/ironic/images/
rsync -av $OLD_CONTROLLER_NODE:/var/lib/ironic/master_images/ /var/lib/ironic/master_images/

新バージョンのController Nodeでサービス再開します

start ironic-api
start ironic-conductor

以下のような動作確認をします。

MY_IP=`hostname -i`
TOKEN=$(openstack token issue -f json | jq -r .id)
curl -g -i -X GET http://$MY_IP:6385/v1/nodes -H "X-OpenStack-Ironic-API-Version: 1.9" -H "User-Agent: python-ironicclient" -H "Content-Type: application/json" -H "Accept: application/json" -H "X-Auth-Token: $TOKEN"

問題なければDNSを更新してendpointを新バージョンに切り替えます。 最後にaggregateに新しいController Nodeを追加、古いController Nodeを削除して終了です。

nova aggregate-add-host $AGGREGATE_ID $NEW_CONTROLLER_NODE
nova aggregate-remove-host $AGGREGATE_ID $OLD_CONTROLLER_NODE

その他

インタフェース構成の改善

一連のアップグレード作業を通じて、いくつか改善したいことが見つかりました。一つはCompute Nodeのインタフェース構成です。現状は以下の図の「現状の構成」のようにbr-exにIPアドレスを割り当てています。この構成だとopenvswitchのアップグレードに何らかの理由で失敗しbr-exがdownしたままになるとCompute Node自体が通信できなくなります。弊社ではCompute NodeはCephのStorage Nodeを兼ねているため、インスタンスのI/Oが詰まってしまうことになります。

これを以下の図の「改善案」のようにbr0というlinux bridgeを追加しそこにbr-exを接続しIPアドレスはbr0に割り当てる構成にすることを検討しています。これにより、openvswitchのアップグレードを仮に失敗したとしてもCompute Node自体はbr0のIPアドレスを使って通信できCephには影響を与えなくすることができます。linux bridgeが一つ増えることで性能面では不利になると思われますが、その辺りを検証した後に本番環境に適用していきたいと考えています。

まとめ

弊社で行ったOpenStackアップグレードの手順について紹介させていただきました。弊社では6月、7月にkiloからlibertyへのアップグレードを1回、libertyからmitakaを2回行ったのですが、アップグレード作業中もその後も障害は発生していません。newton以降へのアップグレードは今のところ未定ですが、アップグレードを行った際にはまたその手順を公開したいと考えています。

最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。

recruit

DeNAでは、失敗を恐れず常に挑戦し続けるエンジニアを募集しています。