簡介

在建構 Lava 測試環境的過程中, 體認到了後端存儲對整體穩定性, 效率及可用性的影響.

原測試執行流程為

  1. Deepin repository 內容發生變化
  2. Jenkins 產生新的 image, 供發佈及測試用
  3. Jenkins 批量發送 test job 到 Lava 上, 對 image 進行測試
    0. Lava 執行特定 test job
    0. Lava 下載 image, 解壓 & NFS 分享被測軟件系統給測試機
    0. 測試機啟動
    0. 安裝測試所需 package
    0. 進行測試
    0. Lava 蒐集測試結果
    0. Lava 刪除被測軟件系統及下載的 image

步驟 3.2 是比較費時費資源的步驟, N 個 job 執行 N 次, 並且目前是由執行 Lava 的機器完成, job 一多反應就掉下來了~ 這個問題同時也影響著 test job 開發時間.

步驟 3.2.3 在測試不通過的情況下, 如果能相對低成本的保留被測軟件系統, 並能一直的以相同狀態被重現, 問題查找時間也可以被縮短.

最後, 如果 Lava 機器上的盤出了問題, 整個測試集群就全都跟著停擺, 影響可用性.

以上問題的改進, 必須從存儲機制著手.

配合 COW (Copy On Write) 技術, 讓被測軟件系統的準備只進行一次, 並在每次 job 要被執行時 clone 一份, 這樣, 測試將運行於乾淨的環境下, 同時過程的變動可以被差異保存, 相對低成本.

COW 之下, 還必須要有如 RAID 之類的提供資料存儲的安全性, 減少單盤出問題時造成的系統不可用或資料丟失.

COW 之上, 如果能以網路將存儲分享出去讓測試機使用, 讓 Lava 機器不再需要擔任這個辛苦的工作, 在測試群集加大時才有可能很好的被擴展.

zfs + iSCSI, 滿足了以上需求, 前者提供了資料整體性/一致性/安全性的基本保證, COW 的設計提供了我們需要的管理彈性. 存儲型式方面, 除了 file system 外, 還提供了 zvol (ZFS Volume) 將 pool 中的空間以 block device 的型式呈現, 能更好的重現出被測軟件系統安裝後的情況, 再配合 iSCSI, 除了讓 Lava 機器上的資源能被空下來外, 測試機器的加入也變得簡單許多.

流程變動如下

  1. Deepin repository 內容發生變化
  2. Jenkins 產生新的 image, 供發佈及測試用
    0. 於 storage server 的 zfs 上配置空間, 產生新的 zvol, 通過 iSCSI 分享出
    0. 將 image 裝上 iSCSI 產生被測軟件系統
  3. Jenkins 批量發送 test job 到 Lava 上
    0. Lava 執行特定 test job
    0. Lava 請求 storage server 產生一份被測軟件系統的 clone
    0. 測試機啟動
    0. 安裝測試所需 package
    0. 進行測試
    0. Lava 蒐集測試結果
    0. 留存被測軟件系統

2.1 及 2.2 只需在每次產生新的 image 之後執行一次即可.

3.2 可將原本持續數分鐘 4~5G 的磁盤存取 (job 並行時更是會讓 throughput 慢上不只 N 倍), 變成數秒內的操作.

3.4 提供了

  • 後續實現 bisect 自動問題查找時的基石. 被測軟件系統基本立即可用
  • 同被測軟件系統不同硬件的快速交叉比對

本文後續說明以上所述 storage server 如何被實現. 涉及的工作有

  • 安裝及準備基於 zfs 的存儲環境
  • 安裝及準備 iSCSI 網路存儲分享環境
  • 提供網路接口供收發存儲請求. 這部份以簡單的 shell script 驗証概念, 請見

安裝 zfs

參考這頁加上 zfs 源

$ su -
$ apt-get install lsb-release
$ wget http://archive.zfsonlinux.org/debian/pool/main/z/zfsonlinux/zfsonlinux_8_all.deb
$ dpkg -i zfsonlinux_8_all.deb
$ gpg --quiet --with-fingerprint /etc/apt/trusted.gpg.d/zfsonlinux.gpg
pub  4096R/4D5843EA 2014-09-24 Turbo Fredriksson <[email protected]>
      Key fingerprint = 5EB3 5C47 B97A C11F 551D  B287 201C 3129 4D58 43EA
uid                            Turbo Fredriksson (Turbo @ Debian GNU/Linux) <[email protected]>
uid                            [jpeg image of size 1983374]
uid                            Turbo Fredriksson (Turbo @ Gmail) <[email protected]>
sub  4096R/9ACF3117 2014-09-24
sub  3072D/6EBBF50F 2014-09-24
sub  4096R/7DFFA34D 2014-09-24

更新包資訊, 並裝上 zfs

$ apt-get update
$ apt-get install debian-zfs

zpool 的建立

接著建立一個 6 盤的 raidz2 pool, 做為主要的存儲中心. 沒那麼多空閒盤的話, 可以先用 sparse file 代替實體盤以完成實驗

$ mkdir zfs+iscsi && cd zfs+iscsi
$ truncate -s 1T disk{1..6}

$ du -sh *
512	disk1
512	disk2
512	disk3
512	disk4
512	disk5
512	disk6

$ ls -lh
total 3.0K
-rw-r--r-- 1 derekdai derekdai 1.0T  6月 16 18:38 disk1
-rw-r--r-- 1 derekdai derekdai 1.0T  6月 16 18:38 disk2
-rw-r--r-- 1 derekdai derekdai 1.0T  6月 16 18:38 disk3
-rw-r--r-- 1 derekdai derekdai 1.0T  6月 16 18:38 disk4
-rw-r--r-- 1 derekdai derekdai 1.0T  6月 16 18:38 disk5
-rw-r--r-- 1 derekdai derekdai 1.0T  6月 16 18:38 disk6

不採用 raidz (RAID 5) 的理由為, raidz 只有一個 redundant, 一旦出現壞盤, 在換上新盤, 進行 re-silvering (從其他舊盤同步資料到新盤的動作) 時到上線提供服務前, 大量的 I/O 很容易就會再出現第二個壞盤, 造成資料丟失. 尤其現今的單盤容量愈來愈大, re-silvering 會更加的長, 出現以上情況的概率只會不斷的提高.

接著建立名叫 iscsi-pool 的 zpool

$ zpool create iscsi-pool raidz2 $PWD/disk{1..6} -O mountpoint=none -O compression=lz4

$ zpool status iscsi-pool
  pool: iscsi-pool
 state: ONLINE
  scan: none requested
config:

	NAME                                STATE     READ WRITE CKSUM
	iscsi-pool                           ONLINE       0     0     0
	  raidz2-0                          ONLINE       0     0     0
	    /home/derekdai/zfs+iscsi/disk1  ONLINE       0     0     0
	    /home/derekdai/zfs+iscsi/disk2  ONLINE       0     0     0
	    /home/derekdai/zfs+iscsi/disk3  ONLINE       0     0     0
	    /home/derekdai/zfs+iscsi/disk4  ONLINE       0     0     0
	    /home/derekdai/zfs+iscsi/disk5  ONLINE       0     0     0
	    /home/derekdai/zfs+iscsi/disk6  ONLINE       0     0     0

errors: No known data errors

$ zpool list iscsi-pool
NAME         SIZE  ALLOC   FREE  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
iscsi-pool  5.97T   480K  5.97T         -     0%     0%  1.00x  ONLINE  -

$ zfs list iscsi-pool
NAME         USED  AVAIL  REFER  MOUNTPOINT
iscsi-pool   272K  3.85T  32.0K  none

pool 的總大小為 6T, 但因為組成 raidz2, 2 個盤用做 redundant, 所以從 zfs list 看到的實際可用空間為 4T.

建立一個叫 images 的 file system, 後續建立的 zvol 都會放在這下面

$ zfs create iscsi-pool/images
$ zfs list -r iscsi-pool
NAME               USED  AVAIL  REFER  MOUNTPOINT
iscsi-pool          145K  3.85T  32.0K  none
iscsi-pool/images  32.0K  3.85T  32.0K  none

在實際運行環境下, 如果有效能上的考量, 也可改以多盤組成 stripe (RAID 0) 以提供最高的 throughput, 再加上 SSD 做 cache, 減少 fragment 發生時帶來的 latency, 並定時以 zfs 的 send & receive 進行遠程差異備份以確保資料不丟失. 不過這麼做的壞處是出現問題時測試環境肯定會出現 downtime.

安裝 targetcli

iSCSI 的 server 端 (存儲提供端) 被稱為 iSCSI target, 運行於 Linux kernel module 中以提供高效率. 要進行管理時, 可配合 targetcli 完成 (或者, 可以看下 /sys/kernel/config/target)

$ apt-get install targetcli

安裝後, 執行 ls 命令確認下目前有無任何 iSCSI 分享

$ targetcli ls
o- / ..................................................................... [...]
  o- backstores .......................................................... [...]
  | o- fileio ............................................... [0 Storage Object]
  | o- iblock ............................................... [0 Storage Object]
  | o- pscsi ................................................ [0 Storage Object]
  | o- rd_mcp ............................................... [0 Storage Object]
  o- ib_srpt ....................................................... [0 Targets]
  o- iscsi ......................................................... [0 Targets]
  o- loopback ...................................................... [0 Targets]
  o- qla2xxx ....................................................... [0 Targets]
  o- tcm_fc ........................................................ [0 Targets]
  o- vhost ......................................................... [0 Targets]

通過 iSCSI 分享 zvol

建立一個 zvol 測試下 iSCSI 分享

$ zfs create -V 16G -s iscsi-pool/images/first-share

$ zfs get type iscsi-pool/images/first-share
NAME                           PROPERTY  VALUE   SOURCE
iscsi-pool/images/first-share  type      volume  -

$ ls /dev/zvol/iscsi-pool/images/first-share -l
lrwxrwxrwx 1 root root 13  6月 17 03:34 /dev/zvol/iscsi-pool/images/first-share -> ../../../zd16

上面的命令使用到了 -s, 產生 sparse zvol, 類似於 sparse file, 或是 thin provision, 因此 iscsi-pool/images/first-share 不會在創建時就佔用 16G 的空間. 另一個效果是 -V 之後指定的大小可以超過 pool 的容量, 詳見 man 8 zfs.

接著配合 targetcli 命令分享出 zvol. 步驟有

  1. 新增 backstore
  2. 新增 iSCSI target
    • 關聯 backstore
    • 新增 portal
    • 新增 acl

新增 backstore

命令如下

$ sudo targetcli /backstores/iblock create first-share /dev/zvol/iscsi-pool/images/first-share
Created iblock storage object first-share using /dev/zvol/iscsi-pool/images/first-share.

targetcli 的命令執行會跟所在的 path 有關 (以上例而言, 命令是 create), path 可以用兩種方式指定

  • 先以 cd 命令切換當前 path 後再執行命令
  • 或是, 如上例, 在命令前先輸入 path (/backstores/iblock) 然後才是要執行的命令

targetcli 支持相當多樣的 backstore

$ sudo targetcli /backstores ls
o- backstores ............................................................ [...]
  o- fileio ................................................. [0 Storage Object]
  o- iblock ................................................. [1 Storage Object]
  | o- first-share ....... [/dev/zvol/iscsi-pool/images/first-share, not in use]
  o- pscsi .................................................. [0 Storage Object]
  o- rd_mcp ................................................. [0 Storage Object]
  • fileio 是將檔案建於 file system 之上, 再通過 iSCSI 當做 block device 分享出去
  • iblock 用做分享塊設備
  • pscsi 用在當下層要分享的設備本來就是 SCSI 存儲時讓 command 能直接下發的情況
  • rd_mcp 是以內存做存儲進行分享, 當有高速非永久存儲需求時可用

因為 zvol 是 block device, 所以選用了 iblock 做 backstore.

新增 iSCSI target

我們讓 targetcli 自動幫我們產生 IQN (iSCSI Qualified Name), 因此命令比較簡單

$ sudo targetcli /iscsi create
Created target iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43.
Selected TPG Tag 1.
Created TPG 1.

$ sudo targetcli ls
o- / ..................................................................... [...]
  o- backstores .......................................................... [...]
  | o- fileio ............................................... [0 Storage Object]
  | o- iblock ............................................... [1 Storage Object]
  | | o- first-share ..... [/dev/zvol/iscsi-pool/images/first-share, not in use]
  | o- pscsi ................................................ [0 Storage Object]
  | o- rd_mcp ............................................... [0 Storage Object]
  o- ib_srpt ....................................................... [0 Targets]
  o- iscsi .......................................................... [1 Target]
  | o- iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43 ..... [1 TPG]
  |   o- tpg1 ........................................................ [enabled]
  |     o- acls ....................................................... [0 ACLs]
  |     o- luns ....................................................... [0 LUNs]
  |     o- portals ................................................. [0 Portals]
  o- loopback ...................................................... [0 Targets]
  o- qla2xxx ....................................................... [0 Targets]
  o- tcm_fc ........................................................ [0 Targets]
  o- vhost ......................................................... [0 Targets]

關聯 backstore

將 iSCSI target 與 backstore 關聯上, 命令如下

$ sudo targetcli /iscsi/iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43/tpg1/luns create /backstores/iblock/first-share
Selected LUN 0.
Created LUN 0.

$ sudo targetcli ls
o- / ..................................................................... [...]
  o- backstores .......................................................... [...]
  | o- fileio ............................................... [0 Storage Object]
  | o- iblock ............................................... [1 Storage Object]
  | | o- first-share ......... [/dev/zvol/iscsi-pool/images/first-share, in use]
  | o- pscsi ................................................ [0 Storage Object]
  | o- rd_mcp ............................................... [0 Storage Object]
  o- ib_srpt ....................................................... [0 Targets]
  o- iscsi .......................................................... [1 Target]
  | o- iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43 ..... [1 TPG]
  |   o- tpg1 ........................................................ [enabled]
  |     o- acls ....................................................... [0 ACLs]
  |     o- luns ........................................................ [1 LUN]
  |     | o- lun0  [iblock/first-share (/dev/zvol/iscsi-pool/images/first-share)]
  |     o- portals ................................................. [0 Portals]
  o- loopback ...................................................... [0 Targets]
  o- qla2xxx ....................................................... [0 Targets]
  o- tcm_fc ........................................................ [0 Targets]
  o- vhost ......................................................... [0 Targets]

新增 portal

Initiator 會通過 portal 查找 target, 有這個設定才能成功將 target 分享出去

$ sudo targetcli /iscsi/iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43/tpg1/portals create
Using default IP port 3260
Automatically selected IP address 10.9.1.207.
Created network portal 10.9.1.207:3260.

$ sudo targetcli ls
o- / ..................................................................... [...]
  o- backstores .......................................................... [...]
  | o- fileio ............................................... [0 Storage Object]
  | o- iblock ............................................... [1 Storage Object]
  | | o- first-share ......... [/dev/zvol/iscsi-pool/images/first-share, in use]
  | o- pscsi ................................................ [0 Storage Object]
  | o- rd_mcp ............................................... [0 Storage Object]
  o- ib_srpt ....................................................... [0 Targets]
  o- iscsi .......................................................... [1 Target]
  | o- iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43 ..... [1 TPG]
  |   o- tpg1 ........................................................ [enabled]
  |     o- acls ....................................................... [0 ACLs]
  |     o- luns ........................................................ [1 LUN]
  |     | o- lun0  [iblock/first-share (/dev/zvol/iscsi-pool/images/first-share)]
  |     o- portals .................................................. [1 Portal]
  |       o- 10.9.1.207:3260 ............................... [OK, iser disabled]
  o- loopback ...................................................... [0 Targets]
  o- qla2xxx ....................................................... [0 Targets]
  o- tcm_fc ........................................................ [0 Targets]
  o- vhost ......................................................... [0 Targets]

新增 acl

指定 iSCSI initiator 的證認資訊. 做為示例, 在此將認證過程關閉簡化操作, 並指定只有特定 initiator 能對這個 target 做寫入.

在此之前, 先裝上 initiator 的支持 (可以在不同機器上安裝)

$ apt-get install open-iscsi

裝上後, 啟動 open-iscsi 服務, 產生 initiator IQN

$ systemctl restart open-iscsi

$ cat /etc/iscsi/initiatorname.iscsi 
## DO NOT EDIT OR REMOVE THIS FILE!
## If you remove this file, the iSCSI daemon will not start.
## If you change the InitiatorName, existing access control lists
## may reject this initiator.  The InitiatorName must be unique
## for each iSCSI initiator.  Do NOT duplicate iSCSI InitiatorNames.
InitiatorName=iqn.1993-08.org.debian:01:868c5e4c481

有了 initiator IQN 後, 就可以為我們的 target 增加 acl 資訊啦. 首先關掉認證過程

$ sudo targetcli /iscsi/iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43/tpg1/ set attribute authentication=0
Parameter authentication is now '0'.

讓上面的 initiator IQN 在 login 後能進行讀寫

$ sudo targetcli /iscsi/iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43/tpg1/ set attribute demo_mode_write_protect=0
Parameter demo_mode_write_protect is now '0'.

$ sudo targetcli /iscsi/iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43/tpg1/acls create iqn.1993-08.org.debian:01:868c5e4c481e
Created Node ACL for iqn.1993-08.org.debian:01:868c5e4c481e
Created mapped LUN 0.

保存設定

$ targetcli saveconfig
Save configuration? [Y/n]: 
Saving new startup configuration

Target 端的設定大功告成.

測試 iSCSI Target

在安裝了 open-iscsi 的機器上, 執行以下命令確認 iSCSI target 可被找到

$ iscsiadm --mode discovery --type sendtargets --portal 10.9.1.207
10.9.1.207:3260,1 iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43

如果能找到的話, 就可以進行 login 動作

iscsiadm --mode node --login iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43
Logging in to [iface: default, target: iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43, portal: 10.9.1.207,3260] (multiple)
Login to [iface: default, target: iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43, portal: 10.9.1.207,3260] successful.

成功後, 用 lsblk 命令可看到系統中出現了一個新的盤. 以下例而言, sdc 就是通過 iSCSI 分享出來的 zvol

lsblk
NAME    MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda       8:0    0 931.5G  0 disk 
├─sda1    8:1    0   243M  0 part 
└─sda2    8:2    0 931.3G  0 part 
sdb       8:16   0 465.8G  0 disk 
├─sdb1    8:17   0  59.6G  0 part /
├─sdb2    8:18   0  14.9G  0 part [SWAP]
├─sdb5    8:21   0   335G  0 part
└─sdb6    8:22   0    96M  0 part 
sdc       8:32   0    16G  0 disk 

你可以將 sdc 當成一般的 block device 進行任何你會對磁盤做的操作. 用完後, 做 logout 操作 sdc 就會從系統上消失, 但相應的改變已保存到 iSCSI target 端的存儲中了

iscsiadm --mode node --logout iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43
Logging out of session [sid: 2, target: iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43, portal: 10.9.1.207,3260]
Logout of [sid: 2, target: iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43, portal: 10.9.1.207,3260] successful.

刪除 iSCSI Target

兩個命令, 刪除 backstore 及 iSCSI target 並保存即可

$ targetcli /backstores/iblock delete first-share
Deleted storage object first-share.

$ targetcli /iscsi delete iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43
Deleted Target iqn.2003-01.org.linux-iscsi.dd-laptop.x8664:sn.ebbe670cbd43.

$ sudo targetcli saveconfig
Save configuration? [Y/n]: 
Performing backup of startup configuration: /var/target/backup-2016-06-17_04:58:42.lio
Saving new startup configuration

這樣 iSCSI target 應該就沒法被找到了

$ iscsiadm --mode discovery --type sendtargets --portal 10.9.1.207
iscsiadm: cannot make connection to 10.9.1.207: Connection refused
iscsiadm: cannot make connection to 10.9.1.207: Connection refused
iscsiadm: cannot make connection to 10.9.1.207: Connection refused

zfs set shareiscsi

zfs 本身提供了 SAMBA, NFS, iSCSI 的整合. 以 iSCSI 而言, 只要一行命令即可完成上面的分享

$ zfs set shareiscsi=on iscsi-pool/images/first-share

$ sudo targetcli ls
o- / ..................................................................... [...]
  o- backstores .......................................................... [...]
  | o- fileio ............................................... [0 Storage Object]
  | o- iblock ............................................... [1 Storage Object]
  | | o- iqn.2016-06.space.co-op:iscsi.pool.images.first.share  [/dev/zvol/iscsi-pool/images/first-share, in use]
  | o- pscsi ................................................ [0 Storage Object]
  | o- rd_mcp ............................................... [0 Storage Object]
  o- ib_srpt ....................................................... [0 Targets]
  o- iscsi .......................................................... [1 Target]
  | o- iqn.2016-06.space.co-op:iscsi.pool.images.first.share ........... [1 TPG]
  |   o- tpg1 ........................................................ [enabled]
  |     o- acls ....................................................... [0 ACLs]
  |     o- luns ........................................................ [1 LUN]
  |     | o- lun0  [iblock/iqn.2016-06.space.co-op:iscsi.pool.images.first.share (/dev/zvol/iscsi-pool/images/first-share)]
  |     o- portals .................................................. [1 Portal]
  |       o- 127.0.0.1:3260 ................................ [OK, iser disabled]
  o- loopback ...................................................... [0 Targets]
  o- qla2xxx ....................................................... [0 Targets]
  o- tcm_fc ........................................................ [0 Targets]
  o- vhost ......................................................... [0 Targets]

但目前的整合有幾點問題

  • 執行 zfs set shareiscsi=off ... 沒有效果
  • 重啟時無法成功的重新分享出 iSCSI target
  • 使用 hostname 解析後的 IP 為 portal IP, 會造成 initiator 無法連上
    • /etc/hosts 重新對應 hostname 及 IP 可解決此問題
  • man 8 zfs 中有關 shareiscsi 的章節有說明如何在分享時指定額外的參數, 但語法的解析還有問題

因此目前以 shell script 完成 zvol 的 iSCSI 自動分享還是比較穩當的做法.

Web 接口

我用 shell script 粗糙的實現了下通過 web 進行 iSCSI 存儲的配置功能, github 項目在此

https://github.com/x-deepin/zfs-gate

此實驗性質項目提供了

  • zfs-gate.service
    • 在啟動時重新分享出 zvol
    • 啟動 busybox httpd 提供 web 接口
  • 可調用的 web API 請參考 cgi-bin 目錄

zfs 最近已經實現命令行 JSON 輸出, 對於前端管理界面的整合能更方便的進行, 有需要的同學可參考這個討論串

https://github.com/zfsonlinux/zfs/pull/3483

參考資料