OpenStackをアップグレードしたら心臓止まりかけた話

本記事は、[OpenStack Advent Calendar 2015](http://www.adventar.org/calendars/968) の12月3日の記事である。

# はじめに
先日、OpenStack 環境を Juno から Liberty に一気にアップグレードしてみた。

なんでそんなことしたのかっていうと、先日のOpenStack Summit TokyoでPayPalがFolsomからKiloまでアップグレードしたっていうセッション見たら、自分にもできるって思ったからだ。

いや、結果、アップグレードできたんだけどさ。

まぁそんなわけで、見事に人柱になれたと思うので、ここではその時のことを記すことにした。

なお、そんなPayPal様のセッションはこちら。

[PayPal's Cloud Journey From Folsom to Kilo -- What We Learned in the Upgrade](https://www.openstack.org/summit/tokyo-2015/videos/presentation/paypals-cloud-journey-from-folsom-to-kilo-what-we-learned-in-the-upgrade)

# アップグレード前の構成
| Component        | Type                            |
|:-----------------|:--------------------------------|
| OS               | Ubuntu 14.04                    |
| OpenStack        | Juno                            |
| Hypervisor       | KVM                             |
| Neutron Driver   | VLAN + OVS (L3 Agentは動いてない) |
| Cinder           | 知らん                           |

# アップグレード
Keystone -> Glance -> Nova -> Neutronの順番でやるのが一般的なんじゃないだろうか。Design Summitでは、各コンポーネントでバージョンがバラバラだって言う企業もいたから、順番は参考程度なんだが、全コンポーネントをアップグレードするならこの順番がいいと思う。

ちなみに、無停止アップグレードは絶対無理だからアキラメロン。って、Design Summitに居たハッカーのおっちゃんたちが言っとりました。

おっしゃる通りでございます。

__. . . _閑話休題_ . . .__

## ロードバランサの紐付け解除
Keystoneのフロントにあるロードバランサの5000番ポートに対する紐付けを解除し、完全に外部からの通信を止める。35357番ポートはそのままで問題ないと思われる。

## Libertyリポジトリ導入
まずは、全てのサーバにLibertyリリースのリポジトリを入れよう。

```shell-session
$ sudo echo "deb http://ubuntu-cloud.archive.canonical.com/ubuntu trusty-updates/liberty main" > /etc/apt/sources.list.d/cloudarchive-liberty.list
$ sudo apt-get update
```

## Keystone前準備
KeystoneのDBに溜まっているtokenを削除する。LibertyからはデフォルトでMemcachedをtoken用途で利用するようになっているので、そちらを使うことにした。つまり、事前にMemcached用サーバを用意する必要がある。

```shell-session
$ sudo su -s /bin/sh -c "keystone-manage token_flush" keystone
```

DBのTokenを綺麗に消したら次は定期実行していたCronを停止する。

```shell-session
$ sudo crontab -e -u keystone
```

以下の行を削除する。

```
@hourly /usr/bin/keystone-manage token_flush > /var/log/keystone/keystone-tokenflush.log 2>&1
```

潔く、Keystoneを止めましょう。

```shell-session
$ sudo service keystone stop
```

## データベースのバックアップ
DBサーバにログインして以下のコマンドでDBのバックアップを取得する。

```shell-session
$ sudo mysqldump -uroot -p --opt --add-drop-database --single-transaction --master-data=2 keystone > liberty-keytone-db-backup.sql
$ sudo mysqldump -uroot -p --opt --add-drop-database --single-transaction --master-data=2 glance > liberty-glance-db-backup.sql
$ sudo mysqldump -uroot -p --opt --add-drop-database --single-transaction --master-data=2 nova > liberty-nova-db-backup.sql
$ sudo mysqldump -uroot -p --opt --add-drop-database --single-transaction --master-data=2 neutron > liberty-neutron-db-backup.sql
```

## Keystoneアップグレード
ここでKeystoneのアップグレードを行う。いろんなパラメータが新規に追加されたり廃止予定になったりしているが、それについては書ききれないので[_Configuration Reference_](http://docs.openstack.org/liberty/config-reference/content/)を参照してほしい。アップグレード自体は、`apt-get install XXXX`して、その後、`keystone.conf`を書き換えて、プロセスを起動するだけである。基本的には[インストール手順](http://docs.openstack.org/liberty/install-guide-ubuntu/)のとおりに行えば問題ない(はず)。

ちなみに、`conf`にDEBUGオプションを入れていると、プロセス起動直後のログに、`conf`の各項目に何の値が設定されているのかが全て出力される。親切なことに、廃止予定の項目も指摘してくれる。これは全コンポーネント共通の仕組みなので、覚えておいて損はない。そういえば、Novaの`username`という項目で、`username`は廃止予定だから使うのやめて`user-name`を使えってログに出ていたから、使うのやめて`user-name`使ったらエラー吐きまくってNovaが動かなくなった。ツンデレか。

話が逸れた。

`keystone.conf`を配置したら以下のコマンドでLibertyリリースまでDBのスキーマのマイグレーションを行う。Kilo以降、KeystoneはApacheで稼働するようになったので、事前にKeystoneプロセスが止まっていることを確認してApacheを起動しておこう。

```shell-session
$ sudo service keystone stop
$ sudo service apache2 restart
$ sudo su -s /bin/sh -c "keystone-manage db_sync" keystone
```

エラーが出なければKeystoneのアップグレードは完了である。

一応、openstackコマンドが実行可能かどうかを確認しておこう。

```shell-session
$ ADMIN_TOKEN=${keystone.confに記載されているadmin_tokenの文字列}
$ export OS_TOKEN=${ADMIN_TOKEN}
$ export OS_URL=http://${Keystoneが稼働しているどれかのサーバのIPアドレス}:35357/v3
$ export OS_IDENTITY_API_VERSION=3
$ openstack service list
```

## Glanceアップグレード
Keystone同様に_Configuration Reference_や公式のインストールガイドを確認しながら、環境にあった`glance-api.conf`、`glance-registry.conf`を配置しよう。

その後、Glanceの_db sync_を行う。

```shell-session
$ sudo service glance-api restart
$ sudo service glance-registry restart
$ sudo su -s /bin/sh -c "glance-manage db_sync" glance
```

エラーが出なければGlanceのアップグレードは完了である。

## nova-apiアップグレード(Nova _db sync_ 失敗編)
最大の難関である。NovaのDBを上手くLibertyまでマイグレーション出来ればアップグレードできたも同然である。がしかし、現実はそうそう上手くはいかなかった。

なので、この項ではアップグレードが上手くいかなかった時の状況をそのまま記すことにした。

### まずは一気にLibertyまでアップグレードしてみる
まず、`nova-api`が稼働しているサーバ上で、`nova-api`をLibertyまでアップグレードした。

```shell-session
$ sudo apt-get install nova-api python-novaclient
```

その後_db sync_を行って、問題なければそのまま他のコンポーネントもアップグレードして完了となる(はずだった)。

で、以下のコマンドを叩くのだが、

```shell-session
$ sudo su -s /bin/sh -c "nova-manage db sync" nova
```

エラー発生:(

### 奇妙なnova-manageのオプション
エラーの内容としては、

* _db sync_ する前に`nova-manage db migrate_flavor_data`というコマンドを実行しろ

である。

が、__実はLibertyリリースの`nova-manage`コマンドにはこのオプションが入ってないのである__。 ソースコードまで読んで調べたが本当になかった。んなアホなと思ったんだが、この時点でDBのスキーマ自体はかなり中途半端に変更されてしまっていた。

この状態で以下のことを行った。

* DBをドロップ(1度目)してリストア
* リポジトリをJunoリリースまで戻してパッケージを再インストール

次に疑ったのが、Kiloリリースである。なので、以下のことを行った。

* Kiloリリースの`nova-api`インストール
* `nova-manage db migrate_flavor_data`実行
* `nova-manage db sync`実行

Kiloリリースのリポジトリを追加してインストールした`nova-api`には`nova-manage`のオプションに`migrate_flavor_data`があった。キタコレ！と思って、さっそく`nova-manage db migrate_flavor_data`を実行した。そしたら成功したもんだから、そのまま一旦Kiloリリースまで上げてからLibertyリリースにしようと思って、Kiloリリース時点まで_db sync_した。

そしたら、またエラーが出た:(

_flavor_ のテーブルのスキーマがおかしいという旨のエラーだった。嘘だろって思ったがマジだった。エラーの内容と状況を整理すると、__Kiloリリースの_db sync_の後で、かつLibertyリリースの_db sync_の前に、`nova-manage db migrate_flavor_data`を実行しろ__、ということである。

つまり、正解はこうだ。

* Kiloリリースの`nova-api`インストール
* `nova-manage db sync`実行
* `nova-manage db migrate_flavor_data`実行
* Libertyリリースの`nova-api`インストール
* `nova-manage db sync`実行

### _apt_ 壊れた
この時点でまたもやDBのスキーマは中途半端に変更されてしまっていたため、Junoリリース時点まで戻して、またDBのドロップ(2度目)とリストアを行った。その後、Junoリリース時点の`nova-api`に戻そうとしたのだが、__今度は_apt_が壊れた....__。

JunoとKiloとLibertyを何度も行き来していたせいで、依存パッケージがおかしなことになりインストールも削除もできなくなってしまった。しょうがないので、`dpkg`コマンドで全3バージョン(Juno, Kilo, Liberty)のNovaに依存が貼られている依存パッケージ全てを削除した。そしたら_apt_が動くようになったので、またJunoリリースまで戻して再チャレンジすることとなる。

実はこれやってる最中に、この前テレビでやっていた「学校へ行こう!」の軟式globeの曲が脳内でループしていた。

「アホだなぁー」「そうだよアホだよ」

名曲である。

__. . . _閑話休題_ . . .__

## nova-apiアップグレード(Nova _db sync_ 成功編)
ここまでを踏まえて、成功した手順を記す。

Kiloリリースをインストールする。

```shell-session
$ sudo echo "deb http://ubuntu-cloud.archive.canonical.com/ubuntu trusty-updates/kilo main" > /etc/apt/sources.list.d/cloudarchive-kilo.list
$ sudo apt-get update
$ sudo apt-get install nova-api python-novaclient
```

Kiloリリース時点のスキーマまでマイグレーションする。

```shell-session
$ sudo service nova-api restart
$ sudo su -s /bin/sh -c "nova-manage db sync" nova
$ sudo su -s /bin/sh -c "nova-manage db migrate_flavor_data" nova
```

その後、Libertyリリースに上げる。

```shell-session
$ sudo echo "deb http://ubuntu-cloud.archive.canonical.com/ubuntu trusty-updates/liberty main" > /etc/apt/sources.list.d/cloudarchive-liberty.list
$ sudo apt-get update
$ sudo apt-get install nova-api python-novaclient
```

Libertyリリース時点のスキーマまでマイグレーションする。

```shell-session
$ sudo service nova-api restart
$ sudo su -s /bin/sh -c "nova-manage db sync" nova
```

これで、`nova-api`のアップグレードとNovaのDBのマイグレーションが完了となる。

ちなみに、このDBマイグレーション手順については、実はKiloのリリースノートに1行だけ記載があった。が、それでもあまり親切な書き方ではないから、事前に確認していてもハマってたと思う。

* [ReleaseNotes/Kilo](https://wiki.openstack.org/wiki/ReleaseNotes/Kilo#Upgrade_Notes_2)

## neutron-serverアップグレード
`nova-api`がアップグレードできたら、次に`neutron-server`をアップグレードする。

Keystone同様に_Configuration Reference_や公式のインストールガイドを確認しながら環境にあった`neutron.conf`や、その他諸々を配置しよう。なお、Open vSwitchは_ml2_の設定ファイルが分割されているので注意が必要である。

その後、Neutronの_db sync_を行う。

```shell-session
$ sudo service neutron-server restart
$ 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
```

エラーが出なければNeutronのDBはLibertyリリース時点のスキーマまでマイグレーションされている。

## Nova/Neutronの残りのパッケージのアップグレード
ここまで完了すれば後は残っているパッケージを全てアップグレードしよう。ここから先は、特に問題は起こらない(はず)である。何をどうやってアップグレードするのかについては各環境に依存するので、ここでは記さないこととする。

## ロードバランサの紐付け
Keystoneのフロントにあるロードバランサの5000番ポートに対してリアルサーバを紐付ける。

## Horizonアップグレード
Horizonは、ぶっちゃけると、他のコンポーネントとバージョンが食い違っていても全く問題ないので、OpenStackがリリースされるたびにアップグレードしても何も問題ない。

が、Libertyリリースに限って言うと、__というか、Libertyリリースの_deb_パッケージに限って言うと__、アップグレードが失敗する。

なぜうまくいかなかったのかについて詳しい事象は判明していない。とりあえず、起こった事象を書き記すことにした。申し訳ない。

### _deb_ パッケージの謎
自分は今回_deb_ パッケージが新規インストール時とアップグレード時で挙動が違うという事象に遭遇した。普通は(きっと)そんなことはない(はず)なのだが、LibertyリリースのHorizonの_deb_パッケージに限って言えば、挙動が違った。

Junoリリースで稼働していたHorizonのサーバで、`apt-get upgrade`、もしくは`apt-get install openstack-dashboard`でHorizonのバージョンを上げた場合、CSSが読み込まれず、Horizonが2000年あたりの何とも簡素なWebページのようなデザインになる。

Horizonもコンポーネントが増えてごちゃごちゃしてきたから、ここで一つ __Simple is Best__ ってことでデザイン変更したのかなって一瞬だけ思ったが、そんなことは全く無かった。いくらなんでも時代に逆行しすぎである。

__. . . _閑話休題_ . . .__

ひとまずアップグレードプロセスに問題があるのだろうと踏んだので、調べるためにLibertyリリースの`deb-src`行をリポジトリに追記して、openstack-dashboardの_deb_パッケージを落としてくる。

```shell-session
$ sudo echo "deb-src http://ubuntu-cloud.archive.canonical.com/ubuntu trusty-updates/liberty main" >> /etc/apt/sources.list.d/cloudarchive-liberty.list
$ sudo apt-get update
```

```shell-session
$ sudo apt-get source openstack-dashboard
```

```shell-session
$ tar zxvf horizon_8.0.0-0ubuntu2~cloud0.debian.tar.gz
$ cd debian
```

`README.source`を開くと早速以下の様なことが書いてあった。

```
During the Juno/14.10 development cycle, use of xstatic packages was introduced
so that CSS, JS and other static assets did not have to be embedded in the
horizon source tree.
```

怪しい、すごく怪しい。

ついでに、Icehouseリリースに遡ってソースを落としてくると、今度は`README.compression`というファイルに以下のことが書いてある。

```
Until this can be scripted and integrated into package build, updating the
pre-compressed static CSS and JS requires a some manual steps:

   sudo apt-get install python-lesscpy python-openstack-auth python-compressor
   quilt pop top
   ./debian/rules refresh-static-assets
```

ふむ。

`rules`ファイルとかちゃんと読めば、きちんとした原因が判明するだろう。

が、僕はこの時点で完全に力尽きていた。

唯一分かっていることは、一度JunoリリースのHorizon(Django含む)のパッケージを全て削除してLibertyリリースのパッケージを入れなおせば、問題なく動くということである。なお、この事象は、JunoからKiloに上げた際には発生しない。JunoからLiberty、もしくはKiloからLibertyにアップグレードした場合にのみ発生するのである。

というわけで、詳細知ってる人がいたら教えて下さい、お願いします;)

あと、Canonicalの中の人はこの_deb_パッケージどうにかして下さい:-(

## アップグレードの終わり
ここまででOpenStack環境のアップグレードが完了となる。当然だが、こんな作業をマニュアルでやっていたら、いつか__腱鞘炎__になる。それが嫌なら、ChefとかPuppetとかAnsibleとかそういうの使ってやったほうがいい。たぶん。

お疲れ様でした:-)

# その後の話
## RFC3442(クラスレス静的ルート)問題
これは`neutron-dhcp-agent`のお話である。皆さん、RFC3442をご存知だろうか？僕は最近まで知りませんでした(すいません)。基本的にアドレスやルートの配布はDHCPに頼ることとなる。そこで、DHCPの仕様を拡張し、IPアドレスをアサインする際に静的ルートテーブルも同時に配布する仕組みが導入された。それが、『クラスレス静的ルート』(RFC3442)である。

[RFC 3442 - The Classless Static Route Option for Dynamic Host Configuration Protocol (DHCP) version 4](http://tools.ietf.org/html/rfc3442)

Junoリリース時点でRFC3442実装に対する対応はされていたそうだが、どうやらきちんと動いていなかったようだ。それがKiloリリース時点でバグ改修されており、見事Libertyリリースではちゃんと動くようになったのである。

何がどう問題だったのかというと、この実装がキチンと動いたことにより、使用していたExternal Networkのサブネットの組み方が間違っていたことが判明したのである。どう間違っていたのかを記すことは止めておくが、一つ言えるのはとにかく大変だったということである。

サブネットについてはネットワークのCIDRを変更すると事象が治るということが分かったのだが、Neutron APIからCIDRの変更が出来ないということも判明した。仕方ないので、Neutronデータベースのsubnetsテーブルに対して直接UPDATE文を実行する、という最終手段を取ることで解決した(おい)。

# アップグレード作業から得た教訓
### 事前の検証は絶対必要
本番環境でのアップグレードを実施するまでに、本番と同等構成による新規でLiberty構築を1回と、事前にJunoの同等構成の検証環境を作っておき、そこからのアップグレードテストを1回やった。ちなみに今回の話は検証環境で起こった話です。おかげで本番環境ではすんなりアップグレードが出来ました。

### どうしてもアップグレードが必要というわけでないのならやらないに越したことはない
OpenStackの各バージョンはリリースから約14ヶ月で[EOL](http://docs.openstack.org/releases/)を迎える。つまり、リリースから約14ヶ月はBugfixがバックポートされるので、それでお茶を濁すというのもあり。もしくは、リリースタイイングごとにKeystoneだけアップグレードして、その他のコンポーネントはRegionをきちんと定義して小さくOpenStackクラスターを作っていくという手もある。当然、構築したOpenStackクラスターそのもののEOLを定義していく必要がある。

### 問題はアップグレード後にも起こる
アップグレード後に初めて気付く問題もたくさんある。事前にLaunchpadからバグやKnown Issueは確認して、アップグレードをするかどうかの判断基準にするとよい。とはいえ、リリース直後にアップグレードするとなると、それはただの人柱であり、バグがそもそもLaunchpadに登録されてない、なんてこともある。

# 最後に
ちなみに今日は僕の誕生日。