19 June 2016

Ubuntu 16.04 (xenial) 已經推出兩個月了,不過到了這週才有機會嘗試著從 14.04 升到 16.04。原因是卡在 Vagrant 遲遲不支援新的 ubuntu cloud image。現在 DevOps 的工作流程多半是先在本機端嘗試用 Vagrant 配置新的 OS,等全部驗證過後,才會開始在 cloud 上實行。某方面來說,Vagrant 變成了一個工作流程上的新的瓶頸,它如果不支援,你就無法進行下一步。

所幸,Vagrant 1.8.3 終於發行了,正好 16.04 的毛邊也修的差不多了吧,該來實際試試,從 14.04 LTS 轉移到 16.04 LTS。

Ubuntu cloud image for vagrant 的變更

先從 Vagrantfile 開始,配置 Ubuntu cloud image:

Vagrant.configure(2) do |config|
  # omit...

  config.vm.box = "ubuntu-16.04"
  config.vm.box_url = "https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-vagrant.box"

  # omit...
end

Vagrant 本身也有做 xenial 的 box,但因為我們的目標是配置到 cloud ,所以會希望本機的測試環境越像 cloud 裡的越好。所以我們這裡使用 Ubuntu 本家自己做的 cloud image。注意,16.04 的 cloud image for vagrant 和 14.04 的 不一樣。在 14.04 的時代,vagrant up 之後,預設帳號是 vagrant,你也有 /vagrant 這個共享路徑可用。這一次的 cloud image 加了 Cloud-init,預設帳號也變成 ubuntu,然後 /vagrant 目錄也不見了。如果你的配置流程裡依賴這兩者,那就要跟著改變。我們整理一下 16.04 cloud image 的變更:

  • 預設帳號變成 ubuntu
  • 套用 Cloud-int
  • 移除 /vagrant 目錄

Ansible 動不了

好不容易 vagrant up 啟動了 VM,下一步就是開始配置主機上的軟體設定,我們是選用 Ansible 這套 python 的工具來進行配置。不過,很不幸的,Ubuntu 16.04 預設只安裝 python3 ,所以第一步 Ansible 就跑不起來。在 Ansible 支援之前,我們必須先安裝回一些必要的套件,在你的 playbook 第一行裡,要加上:

- hosts: all
  gather_facts: false
  tasks:
    - name: ensure python 2, aptitude installed for ansible
      raw: apt install python aptitude -y -q
      become: true

- hosts: foo
  # rest of other roles, tasks...

這個前置作業安裝了 python2 和 aptitude。除了 python2 之外 Ansible 的 apt 模組也需要 aptitude 的 debian 套件,而這個在 16.04 裡被移掉了,所以我們也要裝回去。16.04 的變更整理如下:

  • 預設只安裝 python3.5,沒有 python2
  • 更新至新的 apt 指令,不再依賴 aptitude (more detail)

nginx 支援 http2

預設的 Ubuntu repository 裡的 nginx 已經升到 1.10.0 了,這是相當新的版本,原生支援 http2。你只需要在原本的 nginx ssl 設定檔上,追加 http2 的 flag 即可。

server {
  server_name .kaif.io;
  listen 443 ssl http2;  # <-- http2 here!

  # omit...
}   

支援 http2 是升級到 Ubuntu 16.04 很大的動機。原因是前一版 14.04 裡 openssl 太舊,不支援 ALPN 協定,Chrome 瀏覽器又放棄了 NPN 的舊協定,詳細。你要嘛自己編譯 nginx ,要嘛就是升級到 16.04 才能解決。選哪個方法就看各公司的政策,升了 OS 問題就直接解決了。

anyway, 如果你不是用 Ubuntu 原生的 repository 裡的 nginx,而是用傾向 nginx 的 repo。記得將 trusty 字串換成 xenial,以下是 Ansible 的指令:

- name: Install nginx stable repository
  apt_repository: repo='deb http://nginx.org/packages/ubuntu/ xenial nginx' state=present

MySQL 5.5 升到 5.7

MySQL strict mode

這個改超大!MySQL 5.7 最大的改變是預設開啟 strict 模式 (詳細文件)。我在升級時,就踩到 strict 模式裡的 ONLY_FULL_GROUP_BY 的問題,以前像是下面的 SQL 是可以用的:

select distinct(topic) 
  from forum 
 order by id desc 
 limit 10; 

ONLY_FULL_GROUP_BY 模式下,order by 的項目也必須出現在 select 裡才行。這不是 MySQL 的錯,而是 ansi 就是這樣規定,只是以前我們可以在 MySQL 裡亂寫… 。這個問題視應用程式而定,運氣好的話,只有幾個 SQL 需要改寫,運氣不好的話 (比方說沒有 test),那只好開大絕先關閉 strict 模式,在你的 my.cnf 設定檔裡加上 sql_mode=""

[mysqld]
sql_mode = ""

我個人當然不建議各位這樣做了,MySQL 過去就是因為鬆散的檢查機制才會讓大家垢病這麼久,好不容易開了 strict 模式回到正軌,又把它關掉那就是走回老路了。建議的作法是先不要急著升級 OS 了,然後將 strict mode 一個個打開,看看哪邊壞了就修正,你很有可能會發現過去潛藏已久的 bug 也說不定。全部搞定後,再進行升級。

除了 ONLY_FULL_GROUP_BY 之外,我個人的專案裡還有踩到 timestamp 預設值的問題。過去可以這樣寫:

CREATE TABLE forum (
  id          BIGINT PRIMARY KEY,
  create_date TIMESTAMP NOT NULL DEFAULT 0  /* 錯誤 */
)

timestamp 預設值其實最小是 1970-01-010000-00-00 的話是不合理的數值,所以修改成 1970-01-02 即可。

MySQL 目錄結構變更

原本在 14.04 裡,MySQL 的相關檔案只放在 /var/lib/mysql 裡。而我們如果佈署到 cloud 時,通常會配置另外的硬碟,掛在 /mnt/ 或是 /volume/ 下,然後將 MySQL 的資料檔搬到新的目錄,類似這樣的步驟:

# 這是 pseudo 的步驟:
mkdir /mnt/lib
mv /var/lib/mysql /mnt/lib
ln -s /mnt/lib/mysql /var/lib/mysql

# 最後再修改 /etc/apparmor.d/usr.sbin.mysqld 裡的權限

原本只要搬一個目錄,在 16.04 裡,變成了四個目錄:

/var/lib/mysql
/var/lib/mysql-files
/var/lib/mysql-keyring
/var/lib/mysql-upgrade

這四個我都跟著搬了,整個完整的步驟可參考這裡。另外,與 14.04 不同的是,改完 apparmor 權限後先要重啟一次,才能重開 mysql service,不然不會生效。

Postgresql 9.3 升到 9.5

相對於 MySQL,Postgresql 就好升很多,postgresql.conf 設定檔改幾行就行了。我遇到的是 checkpoint_segments 這個參數被拿掉了,換成用 max_wal_size 取代,你可以在手冊 上查到怎麼調整。

postgresql.conf 兩個版本間的差異很小,你可以參考我的 merge commit

MySQL 的升級是減少錯誤,Postgresql 的升級則是增加功能,苦等以久的 upsert 終於在 9.5 上可以使用了。原本在過去你只能用類似 hack 的方式:

  WITH Upsert 
    AS (
            UPDATE Foo 
               SET voteCount = voteCount + 1 
             WHERE id = 33 
         RETURNING * 
       ) 
INSERT 
  INTO Foo 
       (id, voteCount) 
SELECT 33, 0 
 WHERE NOT EXISTS (SELECT * FROM Upsert) ;

現在改成

      INSERT 
        INTO Foo 
             (id, voteCount) 
      VALUES (33, 0) 
 ON CONFLICT (id) 
   DO UPDATE 
         SET voteCount = Foo.voteCount + 1 ;

直覺很多,而且這個 upsert 還是 atomic/transactional safe,爽!Postgresql 9.5 還新增很多 json 的功能 ,這個版本是個很值得的升級。

那怎麼升級主機… ?

Ubuntu 本身是有指令可以讓你直接從升級 14.04 -> 16.04。但線上服務中的雲端主機我們通常不這樣升,一來是直接升級的指令只會帶來更多問題 (你之前修改的參數在不知不覺中被覆蓋掉),二來,一旦扯到資料庫的變動,就需要有計畫性的慢慢移,而且要規畫停機時間。

我建議是佈署一台新的 16.04 主機,先備份舊的,然後再將服務和資料遷移過去,這樣反而乾淨很多,而且下線時間可以縮到最短。如果過去沒有實行自動化佈署的話,這也是個重頭開始的好機會。

如果不是雲端主機,而是實體伺服器。如果是我就暫時先不升了,14.04 LTS 繼續撐著用。不過應用程式面到是可以先準備好,在測試環境 (vagrant) 裡先試試有什麼問題,提早做好準備。實體主機總是會有遷移的一天 (硬體壞掉,公司換政策… ),等到那一天再一併進行升級的作業。

小結

Ubuntu 已經吃下近 60% 的雲端 OS 的市場了,過去我們有很長的時間是使用 Amazon Linux (CentOS based),現在通通轉到 Ubuntu 上了。14.04 LTS 我們使用上很滿意,正常的服役中,而網路上的資料也多到滿出來,不怕找不到答案,這真是正確的選擇 (大家都同意的)。

對於 16.04,我們則是先完成評估再慢慢的正式上線,靠著 Vagrant 加上 Ubuntu cloud image,模擬線上的環境來測試。Vagrant 很方便,但最近它的釋出紀錄似乎不太好 (上一次是 2015 年底,最近一次是 2016/06,差了半年),它的母公司似乎不再愛它了?短時間內這不會是問題,因為 OS 升級的週期更長,但看到 Vagrant 變成導入新流程的瓶頸總是不太妙… 希望未來能看到改進。

Ubuntu 16.04 最大的改變是 systemd ,不過我沒有討論,因為我還搞不懂它在幹嘛… 所幸的是所有舊有的 service 都能運作正常,因此它不是升級的瓶頸,可以放到之後再處理。反到是應用程式面需要調整,本文列出了我升 MySQL/Postgresql/nginx 時實際遇到的問題和新功能的使用,我自己的小專案有完整的 ansible playbook (kaif.io) 給各位參考。

我們需要等 OS 升級,才能開始享用新版軟體帶來的新功能嗎?當然不是,我們也能在舊版 OS 安裝最新的 Postgresql/nginx...etc ,只是我個人不偏好這樣做罷了。改版底層的軟體茲事體大,Ubuntu LTS 的釋出週期是 2 年,所以我評估的週期也變成兩年一次,這個步調對我個人來說剛剛好。


回響

可以用 Tag <I>、<B>,程式碼請用 <PRE>