從“CI搭建獸”到“流水線即代碼”

本文是2017年3月13日晚9點(diǎn)在“AHA面對面”線上分享的“單件流的力量-伍斌_Ben面對面”的操練步驟,這里是報(bào)名鏈接。

IMG_0861.PNG

操練目的

練習(xí)在CloudBees Jenkins Enterprise上手工配置部署流水線,使得每次代碼提交,都能自動觸發(fā)該部署流水線,并將這個(gè)過程可視化,以便一眼就能看到誰的代碼提交在哪個(gè)環(huán)節(jié)引起了什么質(zhì)量問題,以便快速進(jìn)行修復(fù)。

因?yàn)楸敬尾倬毜闹饕康氖鞘止ご罱ú渴鹆魉€,為節(jié)省時(shí)間,被部署的代碼并不是一個(gè)完整的Web應(yīng)用程序,而是使用了一個(gè)Java應(yīng)用程序和一個(gè)Robot Framework (Python) Web UI自動化應(yīng)用程序:前者僅僅是一個(gè)用Maven創(chuàng)建的有單元測試的簡單Java應(yīng)用,后者僅僅是Robot Framework官網(wǎng)上用于演示用的webdemo應(yīng)用程序。用兩者的結(jié)合來模擬一個(gè)完整的Web應(yīng)用程序:前者模擬自動化單元測試,后者模擬自動化驗(yàn)收測試。

在ThoughtWorks中國區(qū),大家親熱地把用手工搭建部署流水線的人稱為“CI搭建獸”,意指這種手工配置過程既繁瑣費(fèi)神又毫無樂趣,比較原始,比那些已經(jīng)進(jìn)化的人所從事的工作要低級。;-))

而更加高級的工作應(yīng)該是“流水線即代碼”的實(shí)踐,來讓配置腳本能與代碼一起進(jìn)行版本控制。這樣的好處是:Ops可以不用通過訪問生產(chǎn)環(huán)境,就能知道生產(chǎn)環(huán)境上的配置情況;非運(yùn)維人員如Dev,就有機(jī)會去學(xué)習(xí)這些運(yùn)維配置代碼并且加以修改,提升整個(gè)團(tuán)隊(duì)的DevOps能力;另外工具能方便地讀取這些代碼,來自動化地維護(hù)基層設(shè)施,大幅度提升Ops的工作效率。想了解更多相關(guān)的DevOps的良好實(shí)踐,不妨閱讀我的文章“實(shí)例化DevOps原則”。

為了知道“流水線即代碼”到底有多甜,需要先吃一點(diǎn)“CI搭建獸”的苦。本文會先描述“CI搭建獸”的辛苦手工工作,最后會把這些手工工作用10行“流水線即代碼”寫出來并加以運(yùn)行。

準(zhǔn)備工作

環(huán)境準(zhǔn)備

本文以macOS Sierra 10.12.3為例來準(zhǔn)備環(huán)境。

  • 安裝JDK 1.8
  • 安裝Maven 3.3.9
  • 安裝Git 2.10.1
  • 安裝Python 2.7(macOS應(yīng)該自帶)
  • 安裝Python包管理工具Pip 9.0.1
  • 安裝Robot Framework 3.0.2
    pip install robotframework
  • 安裝robotframework-selenium2library
    pip install robotframework-selenium2library
  • 安裝Java IDE IntelliJ 社區(qū)版來編輯Java單元測試代碼
  • 安裝PyCharm 社區(qū)版來編輯Python Web UI測試代碼
  • 準(zhǔn)備測試代碼:可以用下述命令git clone 本次操練的代碼,其中mobilebanking文件夾存放了一個(gè)Java應(yīng)用程序,robotframework-webdemo存放了一個(gè)Web UI測試程序;
    git clone https://github.com/wubin28/jenkins-mobile-banking.git
    • 也可以用下面的方法自己動手來創(chuàng)建和下載這兩個(gè)程序:
      • 用下述Maven命令來創(chuàng)建一個(gè)帶有單元測試的簡單的Java應(yīng)用程序mobilebanking
        mvn archetype:generate -DarchetypeCatalog=internal
      • 下載WebDemo-20150901.zip作為Web UI測試代碼,并將該壓縮包解壓到文件夾robotframework-webdemo
  • 下載CloudBees Jenkins Enterprise,可以選擇On-Premise來下載WAR包(v 2.46.2.1)

單獨(dú)運(yùn)行自動化單元測試

在配置流水線前,先看看Java應(yīng)用程序的單元測試能否運(yùn)行通過。

  • 打開命令行窗口,進(jìn)入上述mobilebanking所在的文件夾,執(zhí)行下面命令,
    mvn clean test

單獨(dú)運(yùn)行自動化Web UI測試

再看看Python的自動化Web UI測試程序能否正常運(yùn)行。

  • 在命令行窗口中進(jìn)入上述robotframework-webdemo文件夾,運(yùn)行下面的命令來啟動一個(gè)待Web UI測試的Web應(yīng)用程序
    python demoapp/server.py
  • 用瀏覽器訪問下面的鏈接,來查看待測試的Web應(yīng)用程序;如果想成功登錄這個(gè)Web頁面,那么用戶名請?zhí)顚?code>demo,密碼請?zhí)顚?code>mode
    http://localhost:7272/
  • 在命令行窗口的robotframework-webdemo文件夾中,運(yùn)行下面的命令來單獨(dú)運(yùn)行Web UI自動化測試。如果運(yùn)行時(shí)發(fā)現(xiàn)瀏覽器驅(qū)動的錯(cuò)誤,則需要另行下載安裝相應(yīng)的瀏覽器驅(qū)動程序,并配置到PATH環(huán)境變量里(例如,從chromedriver網(wǎng)站上下載Chrome瀏覽器驅(qū)動.ZIP文件,解壓后,把相應(yīng)的目錄位置放到~/.bash_profile里面的PATH中,再source ~/.bash_profile使其生效)
    robot login_tests

運(yùn)行CloudBees Jenkins并查看插件

再看看流水線所依賴的兩個(gè)插件是否已安裝。

  • 運(yùn)行下述命令來啟動CloudBees Jenkins
    java -jar jenkins.war
  • 打開瀏覽器訪問下面的CloudBees Jenkins鏈接,在頁面上選擇Trial來申請14天的免費(fèi)試用許可證
    http://localhost:8080/
  • 在首頁上點(diǎn)擊“Jenkins -> Manage Jenkins -> Manage Plugins -> Installed”來查看下面兩個(gè)插件是否已經(jīng)安裝(CloudBees Jenkins Enterprise默認(rèn)已經(jīng)安裝)
    • Delivery Pipeline Plugin
    • Robot Framework plugin

CI搭建獸的辛苦手工工作

先看看要搭建的流水線的樣子。這個(gè)流水線有兩個(gè)Stage:一個(gè)是COMMIT,用來針對第#53號代碼提交運(yùn)行自動化單元測試;另一個(gè)是ACCEPTANCE,用來在單元測試運(yùn)行通過后,針對同樣的代碼提交運(yùn)行基于Web界面的自動化驗(yàn)收測試。


要搭建的流水線的樣子

創(chuàng)建流水線的第一個(gè)Stage:COMMIT

首先創(chuàng)建流水線的第一個(gè)Stage——COMMIT,來運(yùn)行自動化單元測試。

  • 在Jenkins首頁點(diǎn)擊“New Item”鏈接
  • 在Enter an item name輸入框中輸入這個(gè)item的名字,比如可以叫mobilebanking-commit
  • 點(diǎn)擊"Maven project",然后點(diǎn)擊OK按鈕,進(jìn)入配置頁面
  • 勾選“Delivery Pipeline configuration”,在下面出現(xiàn)的Stage Name輸入框中,填寫這個(gè)stage的名字,比如叫COMMIT;在下面的Task Name輸入框中,填寫這個(gè)stage的描述信息,比如叫Build mobilebanking and run unit tests
  • 在Source Code Management框中,選擇Git,然后在下面的Repository URL輸入框中填入Java源代碼的位置,可以是github的地址,這里填寫了本機(jī)地址file:///Users/twer/OOR/katas/remote/jenkins-mobile-banking
  • 在Build Triggers框中,勾選“Poll SCM”,并在Schedule框中填寫* * * * *,表示每分鐘Jenkins會查看是否有代碼提交進(jìn)而觸發(fā)自動化單元測試;* * * * *這種寫法對這次操練很有用,但在實(shí)際工作環(huán)境中不建議使用,因?yàn)檫@會增大服務(wù)器的負(fù)載。在工作環(huán)境中推薦用類似這樣的格式H/5 * * * *,表示每5分鐘,Jenkins會按Job名字的Hash來分散Poll(輪詢)該Job的SCM,已達(dá)到負(fù)載均衡的目的
  • 在Build框中,向Root POM輸入框中填寫pom文件的位置mobilebanking/pom.xml,向Goals and options框中填寫Maven的命令參數(shù)clean test來運(yùn)行單元測試
  • 點(diǎn)擊頁面左下角的“Save”按鈕來保存

讓COMMIT Stage單獨(dú)自動觸發(fā)

咱們需要試一試COMMIT Stage能否隨著代碼提交而自動觸發(fā)單元測試。

  • 在IntelliJ里打開Java應(yīng)用程序mobilebanking,隨便加一行無關(guān)緊要的代碼,比如重復(fù)調(diào)用一遍方法checkUsernameAndPassword(username, password);,但前提是單元測試能夠運(yùn)行通過
  • 在命令行中用git命令來提交代碼,然后立即在Jenkins首頁Dashboard上,觀察mobilebanking-commit這個(gè)Item;它會在1分鐘內(nèi)被自動觸發(fā)
git add .
git commit -m "call method checkUsernameAndPassword() twice"

創(chuàng)建流水線的第二個(gè)Stage:ACCEPTANCE

這個(gè)ACCEPTANCE Stage是用來運(yùn)行Robot Framework Web UI自動化測試的。

  • 回到Jenkins的首頁Dashboard,點(diǎn)擊“New Item”鏈接
  • 在Enter an item name輸入框中輸入這個(gè)item的名字,比如可以叫mobilebanking-acceptance
  • 點(diǎn)擊"Freestyle project",然后點(diǎn)擊OK按鈕,進(jìn)入配置頁面
  • 勾選“Delivery Pipeline configuration”,在下面出現(xiàn)的Stage Name輸入框中,填寫這個(gè)stage的名字,比如叫ACCEPTANCE;在下面的Task Name輸入框中,填寫這個(gè)stage的描述信息,比如叫Run acceptance tests
  • 在Build框中,點(diǎn)擊“Add build step”,然后選擇“Execute shell”;在隨后出現(xiàn)的Command輸入框中,輸入下面的腳本來運(yùn)行Robot Framework Web UI自動化測試
    robot /<完整路徑……>/robot-framework-demo/WebDemo/login_tests
  • 點(diǎn)擊“Save”保存

將兩個(gè)Stage串起來

現(xiàn)在來把第二個(gè)Stage ACCEPTANCE掛到第一個(gè)Stage COMMIT之后,使得第一個(gè)Stage正常運(yùn)行結(jié)束后,能自動觸發(fā)第二個(gè)Stage繼續(xù)運(yùn)行。

  • 回到Jenkins的首頁Dashboard,點(diǎn)擊mobilebanking-commit進(jìn)入這個(gè)Item的頁面,然后點(diǎn)擊左邊的Configure,在頁面底部的Post-build Actions框中,點(diǎn)擊Add post-build action,選擇Trigger parameterized build on other projects,在Projects to build輸入框中輸入mobilebanking-acceptance,在Trigger when build is輸入框中選擇“Stable”,勾選Trigger build without parameters,點(diǎn)擊Save保存
  • 在IntelliJ里mobilebanking這個(gè)Java應(yīng)用程序中,將上述重復(fù)調(diào)用的方法checkUsernameAndPassword(username, password);刪掉
  • 在命令行中用git命令來提交代碼,然后立即在Jenkins首頁Dashboard上,觀察mobilebanking-commitmobilebanking-acceptance這兩個(gè)Item;它們先后會在1分鐘內(nèi)被自動觸發(fā)
git add .
git commit -m "delete duplicated method checkUsernameAndPassword()"

創(chuàng)建一個(gè)視圖來可視化Deployment Pipeline

用視圖將上面兩個(gè)Stage串起來的配置進(jìn)行可視化,以便方便地看到誰的代碼提交在哪個(gè)環(huán)節(jié)引起了什么質(zhì)量問題。

  • 回到Jenkins的首頁Dashboard,點(diǎn)擊All右邊的"+"號,在View Name輸入框中輸入一個(gè)視圖的名字,比如叫Deployment Pipeline;再選擇"Delivery Pipeline View",點(diǎn)擊OK;
  • 在配置頁面中勾選“Show commit messages”來顯示代碼提交描述信息,勾選“Show test results”來顯示單元測試運(yùn)行情況信息
  • 在Pipelines框中,Component的Name輸入框中輸入該視圖的名字,比如叫“Deployment Pipeline”,在Initial Job輸入框中選擇mobilebanking-commit來作為初始運(yùn)行的Stage;點(diǎn)擊OK保存

讓視圖可視化一次代碼提交

現(xiàn)在用上面創(chuàng)建的Deployment Pipeline視圖,來可視化一次代碼提交

  • 在IntelliJ里打開Java應(yīng)用程序mobilebanking,增加一行代碼來重復(fù)調(diào)用一遍方法checkUsernameAndPassword(username, password);
  • 在命令行中用git命令來提交代碼,然后立即在Jenkins首頁Dashboard的Deployment Pipeline視圖中,觀察整個(gè)視圖;它會在1分鐘內(nèi)被自動觸發(fā);另外如果把瀏覽器調(diào)整到僅占據(jù)屏幕的一半,那么在運(yùn)行ACCEPTANCE Stage時(shí),會在屏幕的另一半看到運(yùn)行Web UI自動化測試的界面
git add .
git commit -m "call method checkUsernameAndPassword() twice again"

讓代碼編譯失敗一次

讓代碼編譯失敗一次,看看流水線有什么變化。

  • 在IntelliJ里打開Java應(yīng)用程序mobilebanking,增加一行代碼來調(diào)用沒有創(chuàng)建出來的方法,比如調(diào)用方法abc();
  • 在命令行中用git命令來提交代碼,然后立即在Jenkins首頁Dashboard的Deployment Pipeline視圖中,觀察整個(gè)視圖;它會在1分鐘內(nèi)被自動觸發(fā),COMMIT變紅,如下圖所示;
git add .
git commit -m "call method abc()"
Screen Shot 2017-03-11 at 5.35.33 PM.png

讓單元測試運(yùn)行失敗一次

讓單元測試運(yùn)行失敗一次,看看流水線有什么變化。

  • 在IntelliJ里打開Java應(yīng)用程序mobilebanking,刪除剛才的代碼abc();;然后讓方法loginWithUsernameAndPassword()返回false,使得單元測試失敗
  • 在命令行中用git命令來提交代碼,然后立即在Jenkins首頁Dashboard的Deployment Pipeline視圖中,觀察整個(gè)視圖;它會在1分鐘內(nèi)被自動觸發(fā),COMMIT變黃,如下圖所示;
git add .
git commit -m "make unit test failed"
Screen Shot 2017-03-11 at 5.43.36 PM.png

讓W(xué)eb UI測試運(yùn)行失敗一次

讓W(xué)eb UI測試運(yùn)行失敗一次,看看流水線有什么變化。

  • 在IntelliJ里打開Java應(yīng)用程序mobilebanking,讓方法loginWithUsernameAndPassword()返回true,使得單元測試能成功
  • 在PyCharm里面打開Python項(xiàng)目robotframework-webdemo,將gherkin_login.robot測試文件中的welcome page should be open改為運(yùn)行失敗,具體改法是將resource.robot文件中的Title Should Be Welcome Page一行,改為Title Should Be Welcome Page 1
  • 在命令行中用git命令來提交代碼,然后立即在Jenkins首頁Dashboard的Deployment Pipeline視圖中,觀察整個(gè)視圖;它會在1分鐘內(nèi)被自動觸發(fā),ACCEPTANCE變紅,如下圖所示;
git add .
git commit -m "make web ui test failed"
Screen Shot 2017-03-11 at 6.29.18 PM.png

讓整個(gè)流水線成功運(yùn)行一次

  • 在PyCharm里面打開Python項(xiàng)目robotframework-webdemo,將resource.robot文件中的Title Should Be Welcome Page 1一行,改回Title Should Be Welcome Page
  • 在命令行中用git命令來提交代碼,然后立即在Jenkins首頁Dashboard的Deployment Pipeline視圖中,觀察整個(gè)視圖;它會在1分鐘內(nèi)被自動觸發(fā),整個(gè)流水線會變綠,如下圖所示;
git add .
git commit -m "make web ui test passed"
Screen Shot 2017-03-11 at 6.36.38 PM.png

10行代碼搞定“CI搭建獸”的全部手工工作

下面看看如何用“流水線即代碼”的實(shí)踐,用10行Groovy代碼搞定“CI搭建獸”的全部手工工作。而這10行代碼都放到一個(gè)名為Jenkinsfile的純文本文件中,下面會配置Jenkins,讓它運(yùn)行這個(gè)文件的Groovy腳本和配置語句。

先在Jenkins的Web UI里面定義一個(gè)流水線

  • 在Jenkins首頁點(diǎn)擊“New Item”鏈接
  • 在Enter an item name輸入框中輸入這個(gè)item的名字,比如可以叫pipeline-as-code
  • 點(diǎn)擊"Pipeline",表示要創(chuàng)建一個(gè)流水線,然后點(diǎn)擊OK按鈕,進(jìn)入配置頁面
  • 在Build Triggers框中勾選"Poll SCM",然后在下面的"Schedule"輸入框中輸入* * * * *,這和前面配置COMMIT Stage一樣,都表示每分鐘Jenkins會查看是否有代碼提交進(jìn)而觸發(fā)流水線
  • 在Pipeline框中的Definition選擇框中,選擇"Pipeline script from SCM",表示Jenkins會從版本控制系統(tǒng)來讀取Jenkinsfile;
  • 在SCM選擇框中選擇"Git",并在下面的Repository URL輸入框中填寫`file://<完整路徑……>/jenkins-mobile-banking,來指定Jenkinsfile所在的版本控制系統(tǒng)
  • 最后在最下方的Script Path輸入框中,填寫mobilebanking/Jenkinsfile,來指定Jenkinsfile的確切位置,點(diǎn)擊"Save"保存

在Jenkinsfile里面編寫Groovy腳本來定義流水線

  • 在Java程序所在的mobilebanking文件夾中,用IntelliJ創(chuàng)建一個(gè)名為Jenkinsfile的純文本文件,并在該文件中插入以下Groovy代碼
node {
    stage('COMMIT') {
        echo 'The COMMIT stage'
        sh 'mvn -f <full path>/jenkins-mobile-banking/mobilebanking/pom.xml clean test'
    }
    stage('ACCEPTANCE') {
        echo 'The ACCEPTANCE stage'
        sh 'robot <full path>/jenkins-mobile-banking/robotframework-webdemo/login_tests'
    }
}

這里,Jenkins一旦見到了node語句,就要為這個(gè)流水線分配executor和workspace,所以如果沒有node語句,流水線就不會被執(zhí)行。

stage語句指定了Stage;echo語句用來在console上打印一句話,方便查看運(yùn)行結(jié)果;sh語句指定了要在Unix/Linux機(jī)器上運(yùn)行一句腳本,如果是在Windows機(jī)器上,則要用bat語句。

在COMMIT Stage里面的sh語句,執(zhí)行了maven命令,來運(yùn)行單元測試,其中mvn命令指定了pom.xml文件的位置;在ACCEPTANCE Stage里面的sh語句,執(zhí)行了Robot Framework的Web UI 自動化測試。

運(yùn)行一下流水線

  • 在運(yùn)行pipeline-as-code流水線前,需要把前面“CI搭建獸”搭建的mobilebanking-commit中Poll SCM里面的* * * * *改為H/5 * * * *,來讓這個(gè)Job每5分鐘執(zhí)行一次,從而當(dāng)有代碼提交時(shí),不會與后面配置的pipeline-as-code流水線同時(shí)執(zhí)行,以便于單獨(dú)觀察流水線
  • 在命令行中用git命令來提交代碼,然后立即在Jenkins首頁Dashboard中點(diǎn)擊"pipeline-as-code",來觀察這個(gè)流水線的視圖;它會在1分鐘內(nèi)被自動觸發(fā),流水線會變綠,如下圖所示;如果把瀏覽器縮小到屏幕的一半,那么就能看到屏幕另一半Web UI自動化測試在打開另一個(gè)瀏覽器來運(yùn)行;
git add .
git commit -m "update Jenkinsfile"
Screen Shot 2017-03-12 at 11.35.52 AM.png
  • 再過幾分鐘,“CI搭建獸”搭建的mobilebanking-commit也會被觸發(fā)執(zhí)行

部署流水線與單件流

單件流指的是,正在制作的產(chǎn)品的各個(gè)模塊,能從最初的對其增加價(jià)值的加工步驟,直接傳遞到下一個(gè)增值加工步驟進(jìn)行加工,并最終被傳遞到客戶手中,在這個(gè)過程中,各個(gè)步驟之間沒有發(fā)生等待或者排隊(duì)的現(xiàn)象(參見《豐田套路》)。

而如果在各個(gè)步驟的傳遞過程中發(fā)生了等待或排隊(duì),那就等同于建立了庫存。軟件開發(fā)中常見的庫存包括排隊(duì)等候開發(fā)的需求、排隊(duì)等候測試的代碼、排隊(duì)等待修復(fù)的缺陷、排隊(duì)等待上線的產(chǎn)品特性、甚至Ticket和郵件系統(tǒng);

隱藏很深的庫存可以由諸如那些有固定期限(比如每月一次)的“用戶驗(yàn)收測試”的流程來造成——月初幾天就開發(fā)測試完畢的產(chǎn)品特性必須要被存放近一個(gè)月,等到月底“用戶驗(yàn)收測試”后才能繼續(xù)往下游走。軟件開發(fā)中的上述庫存就是讓項(xiàng)目延期的最大原因。

企業(yè)如果能做到單件流,那么就相當(dāng)于消滅了庫存,讓價(jià)值在不同環(huán)節(jié)之間流動得最快,進(jìn)而實(shí)現(xiàn)了全局優(yōu)化。

這次操練所搭建的部署流水線,可以作為一個(gè)工具來可視化軟件開發(fā)從代碼提交之后的價(jià)值流。如果代碼在各個(gè)Stage之間都無須排隊(duì)且自動化地流動,那么就實(shí)現(xiàn)了Continuous Deployment,也就是在代碼提交之后實(shí)現(xiàn)了軟件開發(fā)最高效的單件流。


操練成就匠藝
全棧軟件開發(fā)者的技術(shù)操練社區(qū):bjdp.org北京設(shè)計(jì)模式學(xué)習(xí)組
QQ群號:235913915,微信訂閱號:bjdp_org,網(wǎng)站:www.bjdp.org

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • <<互聯(lián)網(wǎng)敏捷DevOps和自動化之5.持續(xù)集成>>持續(xù)集成的價(jià)值是什么?對于開發(fā)和測試人員又意味著什么呢?1.1...
    燕京博士閱讀 2,893評論 0 5
  • 2016年11月份的技術(shù)雷達(dá)中給出了一個(gè)簡明的定義:流水線即代碼 (Pipeline as Code) 通過編碼而...
    lambeta閱讀 1,045評論 0 4
  • 文/鄢倩 2016年11月份的技術(shù)雷達(dá)中給出了一個(gè)簡明的定義:流水線即代碼(Pipeline as Code)通過...
    ThoughtWorks閱讀 1,538評論 3 6
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,537評論 19 139
  • 1.一旦安裝APP程序,啟動頁和圖片都會先加載進(jìn)去,如果舊版本沒設(shè)置啟動頁的話,安裝新版本即時(shí)設(shè)置成功了也不會顯示...
    赤焰軍少帥林殊閱讀 211評論 0 0

友情鏈接更多精彩內(nèi)容