###### tags: `cloud init` `lsa` `container` `ncnu` - Book mode https://hackmd.io/@ncnu-opensource/book [TOC] # cloud init 介紹 <!-- | | vm | container | |--------------|------------------------|----------------------------------------| | 運作平台 | hypervisor | execution engine | | 資料封閉性 | 較高,有 有效的隔離機制 | 較低,沒有隔離 OS 的設定,容易發生問題 | | 易用性 | 較好 | 較低 | | 移植性 | 較差 | 較好 | | 資源應用效率 | 較好 | 較差 | --> :::info **colud-init 為何物?** cloud-init 是可以幫助建立環境的一個 package - 一般情況下,VM 初次開機時我們會需要先自己裝好系統,之後再進去系統做設定 - 在使用 cloud-init 的情況下,我們可以在初次開機時就將想要的檔案或設定與系統一併弄好而不用手動處理 ::: <!-- > FIXME: 客製化是甚麼 、 之前是拿那些工具來客製化 、 演變過程 --> ## 為甚麼我需要 cloud-init - 遷移 vm 到其他的雲可能很麻煩 - 不同雲的配置組態不同 - 已經變成行業標準 - 大部分的雲和 distribution 都支援 cloud-init 作為啟動標準 - 防止 user 使用、維護過時的 image 或是 自己做 image - 可以不用做一些特定的工作 - package 管理 - 帳號管理 - 儲存空間初始化 - ssh key 管理 - 每個雲提供資料的方式都不同 - cloud init 可以標準化資料,讓有支援的雲都可以有一樣的行為 - cloud init 收集完資料之後,會分成 4 個 stage 進行初始化,雖然 cloud init 是一個 package , 但初始化得過程類似**虛擬出一個雲**在對 vm 進行初始化的概念 :::info distribution 小小補充: 最簡單的說發行板( distribution )就是 OS 供應商 ( red hat 、 canonical ) 把(GRUB、 linux kernel 、 package 、 package 管理程式( dpkg 、 rpm ) ...)包裝好的 OS。 <br> 更細分來說在早期, linux kernel 、 開機引導程式( grub ) 、 GUI( gnome )...,都是非開、獨立的,部分有志之士會把這些東西整理好,散佈到網路上,因為有散佈的動作,就被稱作發行版~ [ref](https://www.quora.com/What-is-the-distinction-between-an-operating-system-and-a-distribution-with-regards-to-Linux) ::: ## cloud-init 優點 - state manangerment - 快速完成 deploy - 雖然也可以用 image base workflow - 如果一小時打算出很多版時,rebuild image 花時間 - 正確的配置 cloud-init 可以有和 image-base 的優點 - 透過 cloud-init 把環境安裝完 - 為甚麼不直接包成一個 image ? - 可能會花太多時間,不符合開發成本 - 利用 [lxd](https://linuxcontainers.org/lxd/advanced-guide/#cloud-init) 時可以利用 cloud-init 快速完成初始化 ## cloud init 所需資料組成 ![](https://i.imgur.com/wYf5W9c.png) > ref [ubuntu white paper](https://ubuntu.com/engage/cloud-init-whitepaper) ### 一切資料的起點 ( metadata ) 甚麼是 metadata? - plateform 想告訴你主機的訊息 - metadata - local-ipv4 - public-ipv4 - local-hostname - mac - security-groups - ami-id - ... etc. - userdata - passwd - ssh key - datasource - 使用 meta-data 開機一定要有 metadata - [metadata 來源](https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html#) - 主機供應商的 metadata services ( aka metadata ) - [OpenStack 的 Config drives](https://cloudinit.readthedocs.io/en/latest/topics/datasources/configdrive.html) ##### example 1: 查看 meta-data(以 AWS EC2 為例) ```bash curl http://169.254.169.254/latest/meta-data ``` ![](https://i.imgur.com/mcmVBMi.png) :::info 為甚麼是 169.254.169.254? 因為他是 Link-local address 甚麼是 Link-local address - 只能在同一個 LAN 內進行連線 - router 不應該轉發 link-local address 的流量到外網 - 對於一個 host 來說,在網路設定不可用時,有一個可分配的網段可以使用是非常方便的 - 如果透過 dhcp 分配網路不成功,隨機分配一個 Link-local address - 因為是隨機分配,有可能在加入另一個區網後發現有衝突 - 可以透過廣播 ARP request 來達成偵測衝突,但不一定是最好的解法 - 衝突的機率不高 ( 65024 個 ip ) - [rfc3927 section-4](https://datatracker.ietf.org/doc/html/rfc3927#section-4) - <br> refs: - [rfc3927](https://datatracker.ietf.org/doc/html/rfc3927) - [Link-local address](https://en.wikipedia.org/wiki/Link-local_address) ::: ### 客製化的起點( userdata ) - userdata 顧名思義就是 user 想對 vm 的做的設定、 boot 後的行為 - 通常不是 cloud config 就是 shell script - 執行這個 userdata 的使用者是 root - 指令都不用加 sudo 就會是 superuser 權限 - 需要自己修改檔案權限(想讓特定檔案被非 root 的 group 讀寫 ) - `chown` - `chmod` - 不是必要的設定 - 可加可不加,可以沒有這設定 - userdata 的傳輸格式 - Gzip Compressed Content - 被壓縮過後的資料會在解壓縮後被 cloud init 使用 - 因為 userdata 會被限制在 ~16384 bytes(根據 cloud 的限制) - MIME Multi Part Archive - `cloud-init devel make-mime --list-types` : userdata 支援的 content type ``` text/cloud-boothook text/cloud-config text/cloud-config-archive text/cloud-config-jsonp text/jinja2 text/part-handler text/upstart-job text/x-include-once-url text/x-include-url text/x-shellscript ``` - cloud init 的 content type 主型別都是 text , part-handler 定義的是 content-type 裡的子型別 ::: info MIME Multi Part 的格式 ``` Content-Type: text/html; charset=UTF-8 ``` Content-Type 有兩個型別: - 格式 - `主型別/副型別` - 主型別 - text - application - audio - font - ...etc - 常用的 MIME Multi Part - text/plain - text/css - text/html - text/javascript <br> > MIME TYPE 就像是平時檔案的附檔名,如果修改 MIME TYPE 可能會被瀏覽器誤認成其他型別 確認檔案 type 最好再收到檔案後,再用 `file` 指令去檢查檔案 header > [name=blueT] ::: - 只會在 launch instance 時執行(只有一次) - 假設 stop instance 再 start instance 之後,會發現他依舊沒有執行第二次 - 很適合用來安裝 package 和做初始化 - user 可以定義同時有很多種 content type 的 userdata - 例如剛剛上面介紹的 type 可以同時有 `cloud-config` 和 `x-shellscript`( bash 腳本 ) - 自己寫的 content type,要寫 [part-handler](https://cloudinit.readthedocs.io/en/latest/topics/format.html#part-handler) 在 cloud-init 內處理,不然 cloud-init 預設支援的 content type 沒有我們新增的 conetent type 的話就不會有動 - 可以自訂個別的 content-type 的行為 - 可以在 cloud-init 新增的 content type 是 `text/my-test-content` :::info launch 就是跟雲申請並成功開機一個 vm 意思 ![](https://i.imgur.com/HAUsxuR.png) ::: > MIME multi ptart archive ### userdata content type 種類 #### userdata Script 通常是想用來執行在虛擬機上的 shell script - 格式 - 開頭 `#!` or `Content-Type: text/x-shellscript` ##### example 2: using bash as a userdata(以 AWS EC2 為例) - `cat userdata.sh` ```bash= #! /usr/bin/bash echo "LSA is very good, and hello sun at $(date +%T)" > /LSA_test ``` :::info 甚麼是 shell script? 簡單來說就是可以在 Unix like 的 script , 可以把他想成把連續的指令寫成一個文字檔,讓 bash 可以透過 shebang 識別說是要用哪個程式( awk 、 python2 、 python3 or bash ) 去執行的一個文字檔 refs: - [鳥哥 十二章](http://linux.vbird.org/linux_basic/0340bashshell-scripts.php) - [shebang](https://zh.wikipedia.org/wiki/Shebang) ::: :::success 步驟 1. ![](https://i.imgur.com/wW2U6kx.png) 2. goto step3 ![](https://i.imgur.com/GSHnZSu.png) 3. launch instance ::: #### Include File - 格式 - 開頭 `#include` or `Content-Type: text/x-include-url` - 一連串的 url - 一行一個 - 當執行到這步驟時, cloud init 會把從上面定義的 url 依序把內容抓下來 - 當遇到錯誤時,就會停止下載 <!-- - `/var/lib/cloud/instance` --> #### Cloud Config Data - 格式 - [yaml](https://cloudinit.readthedocs.io/en/latest/topics/examples.html#yaml-examples) - 開頭 `#cloud-config` or `Content-Type: text/cloud-config` - 可能可以設定的設定 - apt upgrade: 可能會希望在 boot 前 update apt source - 設定 apt source mirror list or 更多的 source list - 載入特定 ssh key ##### example 4: add user(以 AWS EC2 為例) ```yaml= #cloud-config users: - default - name: ops plain_text_passwd: 'LSA_good_good' home: /home/ops shell: /bin/bash lock_passwd: false gecos: Ubuntu groups: [sudo, lsa, op] primary-group: ops sudo: ['ALL=(ALL) ALL:ALL'] ssh_pwauth: True ``` ![](https://i.imgur.com/pd1dpVI.png) > 可以用 ops 登入 ##### example 5: install docker(以 AWS EC2 為例) ```yaml= #cloud-config apt: sources: source1: source: deb [arch=amd64] https://download.docker.com/linux/ubuntu $RELEASE stable keyid: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 # keyserver: https://download.docker.com/linux/ubuntu/gpg packages: - docker-ce - docker-ce-cli - containerd.io ``` ![](https://i.imgur.com/gwTrPl7.png) > 安裝了 docker #### Upstart Job - 格式 - 開頭`#upstart-job` or `Content-Type: text/upstart-job` - 會把內容放到`/etc/init` :::info `/etc/init` 裡面放的是 system V 啟動的啟動腳本 ::: #### Cloud Boothook - 格式 - 開頭 `#cloud-boothook` or `Content-Type: text/cloud-boothook` - 會把內容存到 `/var/lib/cloud` - 立即執行,沒有任何機制確保同樣的 hook 只被執行一次 - 在環境變數中 `INSTANCE_ID` 可以提供每一個 hook 都被執行一次的功能 - Cloud Boothook 是最早可用的 hook , 可以是 bash script - 有點類似 `runcmd` - 所有 [module](https://cloudinit.readthedocs.io/en/latest/topics/modules.html) #### Part Handler - 開頭 `#part-handler` or `Content-Type: text/part-handler` - 可以自訂 MIME-type 的 handler 或者覆蓋掉原有的 handler - 想要讓自訂義原本的 handler 也可以在這改,例如說我想要定義原本處理 `x-shellscript` 的 handler 當他執行時的預設直譯器是 zsh - part-handler 存在 vm `/var/lib/cloud/data` 的 handler 檔名會依據當初傳輸 part-handler 的名稱儲存在 vm `/var/lib/cloud/data` :::info 如何新增 part-handler - 新增 userdata ( conetnet type: text/part-handler ) - `list_types()` - 定義支援的 content type - 當這個 userdata ( conetnet type: text/part-handler ) 的段落被讀到時,我們會 call `list_types()` 這個 function,因為 MIME 是依照順序依序讀下去 - part-handler 要在他所處理的 content type 前面被讀到(MIME multi archive) - `handle_part()`: - 實際 handler 執行的地方 - 會在要執行 handler 之前、之後 各呼叫一次 - 透過 ctype 知道現在的執行階段 ##### extra_example - `cat one` ``` jerry chillfeng ``` - `cat two` ``` lsa_public ``` - `cat add_LSA_user.py` ```python #part-handler def list_types(): # return a list of mime-types that are handled by this module return(["text/plain", "text/go-cubs-go"]) def handle_part(data,ctype,filename,payload): # data: the cloudinit object # ctype: '__begin__', '__end__', or the specific mime-type of the part # filename: the filename for the part, or dynamically generated part if # no filename is given attribute is present # payload: the content of the part (empty for begin or end) if ctype == "__begin__": print("my handler is beginning") return if ctype == "__end__": print("my handler is ending") return print ("==== received ctype=%s filename=%s ====" % (ctype,filename)) import os for user in payload.splitlines(): print (" == Creating user %s" % (user)) os.system('sudo useradd -p ubuntu -m %s' % (user) ) print("==== end ctype=%s filename=%s" % (ctype, filename)) ``` - 產生 MIME multi part archive:`cloud-init devel make-mime` - make-mime 這個 subcommand 會接收用 `:` 隔開的檔案格式,並轉換成 userdata - config.yaml(檔名):cloud-config(“text/” mime subtype) - more options: [make-mime](https://github.com/canonical/cloud-init/blob/master/cloudinit/cmd/devel/make_mime.py) ``` cloud-init devel make-mime --force\ -a add_LSA_user.py:part-handler\ -a one:plain\ -a two:go-cubs-go\ > user-data ``` :::spoiler 測試 user-data {%gist efficacy38/8e2d12f15a53486ea785da3900174287 %} ::: [ref](http://foss-boss.blogspot.com/2011/01/advanced-cloud-init-custom-handlers.html) ::: ##### example 4(以 AWS EC2 為例) 假設我們有 userdata (config.yaml),自訂的 shell (script.sh), 可以用 `make-mime` 來產生 `MIME` 的資料格式 - `cat config.yaml` 不一定要叫 config.yaml, 也可以叫 config.yamlLSA123 - cloud-init 並不依賴副檔名辨別 config 型態,而是利用 MIME content-type ```bash= #cloud-config users: - default - name: ops plain_text_passwd: 'LSA_good_good' home: /home/ops shell: /bin/bash lock_passwd: false gecos: Ubuntu groups: [sudo, lsa, op] primary-group: ops sudo: ['ALL=(ALL) ALL:ALL'] ssh_pwauth: True ``` - `cat script.sh` ```bash= #! /usr/bin/bash echo "LSA is very good, and hello sun at $(date +%T)" > /LSA_test ``` - 生成 MIME multi part archive ``` cloud-init devel make-mime\ -a config.yaml:cloud-config\ -a script.sh:x-shellscript\ > userdata ``` :::spoiler userdata {%gist 672131bb5297b8573fb81b920610516f %} ::: ![](https://i.imgur.com/yqnv1bH.png) ![](https://i.imgur.com/QMgvEBk.png) :::success 注意 userdata 要改成 as file ![](https://i.imgur.com/2Osgoup.png) ::: ### vendor data ( 非必要 ) - 基本上 vendor data 跟 userdata 是同一標準(有 cloud-config, script 的 type...) :::info vendor data 注意事項 - 使用者對 vendor data 有最終控制權 - 可以禁用 vendor data - 或禁用 muliti-type 的某種 type - 只會在 launch instance 時執行一次(例如在 aws 按下 launch instance 時就會執行) - vendor data 不應該被用作啟動的必要條件 - user 可以在 userdata 禁用 vendor data ```json= #cloud-config vendordata: {excluded: 'text/part-handler'} ``` - userdata 會覆蓋原先的 vendor data - 如果要 merge vender data - 改成可被擴展的 jsonp 格式 ```json= #cloud-config-jsonp [{ "op": "add", "path": "/runcmd", "value":["my", "command", "here"]}] ``` ::: > vendor data merge 的流程 > JSONP 的格式 > reminder: [merge data] > ts 17:42 (https://cloudinit.readthedocs.io/en/latest/topics/merging.html) ### instance data - 每一個主機商各自偏愛的格式來初始化 vm - cloud-init 當被讀取過 `metadata` 、 `userdata` 、 `vendor data` 後會產生 `/run/cloud-init/instance-data.json` - 標準化 metadata, vendordata, userdata 後產生的 instance data - 可以對所有不同的 distrobution 有相容性 - 在各種平台上都有相同的體驗 - instance data 怎麼 merge 資料 - lcoal stage - base config 1. kernel cmdline 2. run-time 產生的 config ( /run/cloud-init/cloud.cfg ) 3. 寫在 image 裡面的 cloud-config ( /etc/cloud/cloud.cfg ) , 4. build-in 的設定(寫在程式碼裡面的設定檔) 5. metadata - anothers stages 6. userdata 7. vendordata 越後面 merge 的資料,優先度越低,有寫過得設定後者都不能覆蓋 例如: userdata 裡面有 run-cmd , 假設 vendordata 也有 runcmd 這個 module,後者就不會被寫入到 instance data,但也有例外,可以用 jsonP 的格式去 merge。 - cloud-init 會把 `instance data` 經過處理之後產生以下的檔案 - `/run/cloud-init/instance-data.json` - 標準化的資料 - 刪減一些敏感的資料後寫入到這個檔案 - `/run/cloud-init/instance-data-sensitive.json` - root 才可讀的文件 - json blob - 處理過的敏感資料 - userdata 可能包含密碼或其他敏感資料 - `/var/lib/cloud/instance/userdata.txt` - root 才可讀的文件 - 敏感的 raw userdata - userdata 可能包含密碼或其他敏感資料 - `/var/lib/cloud/instance/vendor-data.txt` - root 才可讀的文件 - 敏感的 raw vendor data - vendor-data 可能包含密碼或其他敏感資料 - 為甚麼要這麼做? - 簡單的 json 檔案,可以讓 user 的 script 簡單使用 - 避免消耗網路資源,因為 instance data 已經存在檔案系統了 - 利用 cloud init 已經對各個雲做的 meta-data 最佳化爬取 - 哪裡可以用(可以用 jinja 渲染 userdata 、 vendordata ) - userdata 的 script - `/etc/cloud.cfg` 的資料 - 使用 `cloud-init query` 、 `cloud-init devel render` 的 CLI 工具 - `cloud-init query ds.meta_data.public_hostname` - `cloud-init query --list-keys`: instance 支援的 key - key 可以是 userdata、metadata... - `cloud-init query --list-keys`: instance 支援的 key - key 可以是 userdata、metadata... :::info 在cloud-init v.18.4 後, 可以透過 jinja 模板來渲染 cloud instance metadata ([Instance data](https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html#instance-metadata)) ``` ## template: jinja #!/bin/bash {% if v1.region == 'us-east-2' -%} echo 'Installing custom proxies for {{ v1.region }} sudo apt-get install my-xtra-fast-stack {%- endif %} ``` ::: ::: info `/etc/cloud/cloud.cfg` 裡面的資料是再包成 image 之前就已經設定好的,以下是在 未開機過的 AMI 內看到的 cloud.cfg ![](https://i.imgur.com/i1NnGST.png) https://github.com/canonical/cloud-init/blob/main/config/cloud.cfg.tmpl ::: ### 小小整理 | | Gzip | Mime multi part archive | |-------------------|--------------------|-----------------------------------| | userdata Script | with #! | Content-Type: text/x-shellscript | | Cloud Config Data | with #cloud-config | Content-Type: text/cloud-config | | jinja | ## template: jinja | Content-Type: text/jinja2 | | Include File | with #include | Content-Type: text/x-include-url | | Upstart Job | #upstart-job | Content-Type: text/upstart-job | | Cloud Boothook | #cloud-boothook | Content-Type: text/cloud-boothook | | Part Handler | #part-handler | Content-Type: text/part-handler | | | datasource | metadata | userdata | vendordata | instance data | |------|--------------------------------------------------------------------------|--------------------------------------------------------|--------------------------------------------------------|--------------------------------------------------------|------------------------------------------------| | 功能 | 因為每個雲對 userdata、metadata、vendordata 實現方式都不一樣, cloud init 提供這個介面來定義如何獲取他們 | 告訴機器該去哪裡找 userdata、vendordata 的資料 | 使用者自訂的設定 | cloud provider 希望可以提升用戶使用的設定 | meta-data、userdata、vendordata 加上 datasource的資訊 | | 獲得階段 | local | local | local | local | local | | 寫入階段 | local | network | network | network | local 、 network | | 寫入檔案 | `/var/lib/cloud/instances/<instance-id>/datasource` | `/var/lib/cloud/instances/<instance-id>/meta-data.txt` | `/var/lib/cloud/instances/<instance-id>/user-data.txt` | /var/lib/cloud/instances/<instance-id>/vendor-data.txt | `/run/cloud-init/instance-data.json` | | 提供者 | 本機存在的 dmi message | cloud-provider | user | cloud-provider | 在 boot stages 產生的資料 | :::info **甚麼是 dmi 資訊:** - **SMBIOS ( System Management BIOS )** - 主板和 OS 廠家共同訂出的一套描述硬體資訊的方法 - 資料存在主板上的 BIOS 晶片上 - 只能透過規定的方式存取 - **DMI ( Desktop Management Interface, DMI )** - Desktop Management Task Force(DMTF) - 可以提供系統資訊( 包括 SMBIOS 資訊 ) - 可以使用 `dmidecode` 來查看所有的 dmi 資訊 - 可以查看的資訊 - bios 版本資訊 - system ( 系統資訊 ) - 偵測 datasource ( ec2 ) 時,就是偵測這個類別底下的 uuid 是否開頭是 ec2 - memory ( 記憶體 ) - cache ( cpu 上的 L1 L2 L3 的資訊 ) - connector - slot - chassis ( 製造商 、 OEM 廠商資料... ) refs: [SMBIOS](https://en.wikipedia.org/wiki/System_Management_BIOS) [DMI](https://en.wikipedia.org/wiki/Desktop_Management_Interface) [dmidecode](https://linux.die.net/man/8/dmidecode) ::: ## Boot Stages (以 AWS EC2 為例) 在這裡就是剛剛開頭提到的**虛擬出來的雲**的概念,當我們在處理 cloud-init 的啟動時,我們通常會有 5 個 stage 會執行 ### Generator :::info 小小補充 generator : - genrator 執行時機: 會在 systemd 載入 unit file 之前執行(會在 boot 早期執行 ) - 存放位置 (`/path/to/generator normal-dir early-dir late-dir`) - 作用: 動態產生 Unit file cloud init Generator 啟動流程: 1. `/usr/lib/systemd/system-generators/cloud-init-generator` 的 main() 執行 2. 如果有找到 datasource , 那就把 `/lib/systemd/system/cloud-init.target` 連結到 `/run/systemd/generator.early/multi-user.target.wants/cloud-init.target` 3. 最後基於系統啟動的初始值( [systemctl get-default](https://man7.org/linux/man-pages/man7/bootup.7.html) ) 啟動 target , 所以 cloud-init 就被排入服務了 <br> 參考資料: - [early-dir normal-dir late-dir](https://www.man7.org/linux/man-pages/man7/systemd.generator.7.html) - [generator intro](https://www.freedesktop.org/software/systemd/man/systemd.generator.html) - [more about generator](https://www.man7.org/linux/man-pages/man5/systemd.unit.5.html) - [boot dependency](https://man7.org/linux/man-pages/man7/bootup.7.html) ::: #### systemd 的排程 1. `/usr/lib/systemd/system-generators/cloud-init-generator` 的 main() 1. 檢查是否該放入 systemd 的排程 - 檢查 kernel cmdline 使否有 `cloud-init=disabled` - `ds-identify`這個 shell script 找不到 datasource 只要上述的條件滿足就不會被排程進 systemd 的排程 2. cloud-init-generator 具體做的事 - 偵測 datasource 失敗: - 不把 cloud-init 的 `/lib/systemd/system/` 檔 link 到 `/run/systemd/generator.early/multi-user.target.wants/cloud-init.target` - 刪除 `/run/systemd/generator.early/multi-user.target.wants/cloud-init.target` 的 soft link - 偵測 datasource 成功 - 把 cloud-init 的 `/lib/systemd/system/` 檔 link 到 `/run/systemd/generator.early/multi-user.target.wants/cloud-init.target` 3. 就會發現 cloud-init 的 target 檔都沒有被放到 `/run/systemd/generator.early/multi-user.target.wants/cloud-init.target` 裡面,所以就會導致 cloud-init 不被排入 systemd 的排程 ![](https://i.imgur.com/Nw4l3CN.png) 2. 來看看 local stage 的 systemd 腳本 - `vim /lib/systemd/system/cloud-init-local.serivce` ![](https://i.imgur.com/ebkNlZ0.png) 3. network 階段 - `vim /lib/systemd/system/cloud-init.serivce` ![](https://i.imgur.com/WSQRrKc.png) 4. config 階段 - `vim /lib/systemd/system/cloud-config.target` ![](https://i.imgur.com/cP2qHnk.png) - `vim /lib/systemd/system/cloud-config.serivce` ![](https://i.imgur.com/Tpb1xgu.png) 5. final 階段 - `vim /lib/systemd/system/cloud-final.serivce` ![](https://i.imgur.com/ZVZFds0.png) #### 整理 - 預設 generator 會 enable cloud-init - 如果想要關掉 cloud init 有以下幾種方式 - 存在 `/etc/cloud/cloud-init.disabled` 檔案 - `/proc/cmdline` 存在 `cloud-init=disabled` kernel 指令 - 此外在 container 時,因為讀取 kernel 指令沒有被授權,所以會去讀環境變數`KERNEL_CMDLINE` - 因為有 `/run/systemd/generator.early/multi-user.target.wants/cloud-init.target` -> `/lib/systemd/system/cloud-init.target`, 所以 cloud init 的 4 個 systemd 服務被排進 systemd 的開機流程之中 - `/usr/lib/systemd/system-generators/cloud-init-generator` cloud init 的 generator :::info 具體 generator 做了甚麼是可以參考 `/run/cloud-init/cloud-init-generator.log` ![](https://i.imgur.com/mrJ9llV.png) ::: ### 常常會被用到的組件 - 設定 datasource ( 用以獲得 metadata、userdata、vendordata ) - local stage 執行階段 ![](https://i.imgur.com/bBtXFQ6.jpg) - 其他階段 ![](https://i.imgur.com/tdmEUnJ.jpg) - 設定 config (按照順序排序) ![](https://i.imgur.com/6nr1jDt.jpg) - 順序從 1->4 , 編號大的會覆蓋掉編號小的設定檔 - 在 ec2 時 - cat `/run/cloud-init/cloud.cfg` - `datasource_list: [ Ec2, None ]` ::: info 在 ec2 的 image 裡面發現,沒有這個檔案(`/run/cloud-init/cloud.cfg`) 所以這個檔案應該是 cloud init 自動生成的 ::: :::info 如何修改 kernel cmdline - `/boot/cmdline.txt` - grub 設定 <br /> [get into single user mode](https://askubuntu.com/questions/132965/how-do-i-boot-into-single-user-mode-from-grub) ::: ### Local ![](https://i.imgur.com/O1JdvWu.jpg) #### 簡表: | 項目 | 敘述 | |-----------------|------------------------------------------------- | | systemd service | cloud-init-local.service | | 執行條件 | 當根目錄 mount 成 可讀/可寫 時執行 | | 需要阻止載入的設定 | 不需要網路(boot 程序) | | modules | none | #### local stage 做了什麼? 1. **初始化 cfg class( 載入設定 )**: - 依據順序 - 程式碼內的 code - /etc/cloud/cloud.cfg - /run/cloud-init/cloud.cfg - kernel cmd line 2. **尋找 cache( obj.pkl ):** 確認 instance-id 是否改變、 是否 trust ? - obj.pkl - obj.pkl 就是 (datasource 的 cache) - 放置位置 `/var/lib/cloud/instance` - 如果 instance-id 沒有改變,從 obj.pkl 恢復 datasource 的 cache **甚麼是 cache** :::info 怎麼 check cache? cloud init 藉由 check `instance-id` 來確認是否是第一次開機,cloud init 檢查會這個 instance 是不是新的 instance 和 instance 之間的行為,這個行為被稱為 `check` ::: - cloud init 之前跑過 - 出現情形 - 最常見,之前已經 boot 過,這次開機是第2次 or n 次開機 - file system 被 attach 到另一個 instance - 出現情形 - 從已經被包裝好的 image 開機 - 或者是從別的已經 launch 過的 instance 開機,複製一份 - 衍伸出的問題 - `check` 嚴格依賴於 datasource - 假設 cloud provider 的 metadata service 不穩定或無法得到 instance id 的話會出現問題 - 可能會遭到攻擊 - 如果用 cloud init 配置**實體機器**,如果有心人士可以對 instance 提供新的 metadata(instance-id),那就可以讓 instance-id改變,並依照有心人士的設定去初始化 instance,這在 `nocloud` datasource 發現[這個問題](https://cloudinit.readthedocs.io/en/latest/topics/boot.html#id4) - 解決辦法: - 使 instance-id 就算被改變,也不會再跑一次 cloud init(設定成 **trust** 模式) :::info 甚麼是 trust or check 模式 - trust - 就算偵測到 instance-id 有更新,也不會照著新的 instance data 去設定電腦 - check - 初始設定 - 偵測到 instance-id 更新,照著新的 instance data 去設定電腦 <br> 如果有以下的設定,可以設定 cloud init 模式是 trust ( ==**check**== -> ==**trust**== ) - `manual_cache_clean: trust` in `/etc/cloud/cloud.cfg` - 存在`/var/lib/cloud/instance/iid/manual_clean` ::: 3. **初始化 datasource** 如果是第一次跑 cloud init 或 cache 裡面沒有 datasource 的話,就會從 cfg 這個 class 抓出定義好的 datasource ( local ) ,一個一個測試,並初始化 datasource - 通常不同的平台或主機供應商 ( gcp or aws ) 都會有自己偏好的 api 作法,所以會在這個 statage 定義好使用的 datasource - `/usr/lib/cloud-init/ds-identify` : 透過 DMI 資訊確定平台 - ec2 是依靠 vm 的 dmi uuid 開頭是 ec2 來確認應該套用的是 ec2 的 datasource - `/run/cloud-init/ds-identify.log` : 確認 datasource 的平台 :::spoiler log 詳細內容 ```bash= [up 2.37s] ds-identify policy loaded: mode=search report=false found=all maybe=all notfound=disabled /etc/cloud/cloud.cfg.d/90_dpkg.cfg set datasource_list: [ NoCloud, ConfigDrive, OpenNebula, DigitalOcean, Azure, AltCloud, OVF, MAAS, GCE, OpenStack, CloudSigma, SmartOS, Bigstep, Scaleway, AliYun, Ec2, CloudStack, Hetzner, IBMCloud, Oracle, Exoscale, RbxCloud, UpCloud, None ] DMI_PRODUCT_NAME=HVM domU DMI_SYS_VENDOR=Xen DMI_PRODUCT_SERIAL=ec2161c6-805b-7015-116e-3c2d5907b626 DMI_PRODUCT_UUID=ec2161c6-805b-7015-116e-3c2d5907b626 PID_1_PRODUCT_NAME=unavailable DMI_CHASSIS_ASSET_TAG= FS_LABELS=cloudimg-rootfs ISO9660_DEVS= KERNEL_CMDLINE=BOOT_IMAGE=/boot/vmlinuz-5.4.0-1045-aws root=PARTUUID=5198cbc0-01 ro console=tty1 console=ttyS0 nvme_core.io_timeout=4294967295 panic=-1 VIRT=xen UNAME_KERNEL_NAME=Linux UNAME_KERNEL_RELEASE=5.4.0-1045-aws UNAME_KERNEL_VERSION=#47-Ubuntu SMP Tue Apr 13 07:02:25 UTC 2021 UNAME_MACHINE=x86_64 UNAME_NODENAME=ip-172-31-18-216 UNAME_OPERATING_SYSTEM=GNU/Linux DSNAME= DSLIST=NoCloud ConfigDrive OpenNebula DigitalOcean Azure AltCloud OVF MAAS GCE OpenStack CloudSigma SmartOS Bigstep Scaleway AliYun Ec2 CloudStack Hetzner IBMCloud Oracle Exoscale RbxCloud UpCloud None MODE=search ON_FOUND=all ON_MAYBE=all ON_NOTFOUND=disabled pid=148 ppid=125 is_container=false is_ds_enabled(IBMCloud) = true. is_ds_enabled(IBMCloud) = true. ec2 platform is 'AWS'. check for 'Ec2' returned found Found single datasource: Ec2 [up 2.42s] returning 0 ``` ::: :::spoiler [datasources](https://cloudinit.readthedocs.io/en/latest/topics/datasources.html#known-sources) * Alibaba Cloud (AliYun) * Alt Cloud * Azure * CloudSigma * CloudStack * Config Drive * Digital Ocean * E24Cloud * Amazon EC2 * Exoscale * Fallback/None * Google Compute Engine * MAAS * NoCloud * OpenNebula * OpenStack * Oracle * OVF * Rbx Cloud * SmartOS Datasource * UpCloud * ZStack * Vultr ::: 4. 寫入 **instance-data:** 把從 datasource 得到得 instancedata 寫入 `/run/cloud-init-sensitive.json` 和 `/run/cloud-init-sensitive.json` - 目的: 主要是為了提供給下一階段 ( network ) 的 vendordata 和 userdata 來源 5. **啟用網路** - 根據 network 設定和 distro 內定義的 renderer config 設定網路 - ubuntu: netplan ( 預設 ) - 會依照以下順序讀取網路設定,並依照順序套用網路設定,如果以下有找到設定 `network: disable` 就放棄設定網路( none ),如果都沒發現設定的話就設定成 fallback 模式 1. kernel cmdline 內的 network 設定 - `ip=` - `network-config=<Base64 encoded YAML config string>` - `network-config=disabled` 2. /etc/cloud/cloud.cfg 檔內的 network 設定 ``` network: config: disabled ``` 3. datasource 、 distro 內的 network 設定 ![](https://i.imgur.com/wpM6mz0.png) [code 來源](https://github.com/canonical/cloud-init/blob/b5aecbe9512fa546255cc93b178b4081342fc247/cloudinit/distros/ubuntu.py#L25) ##### 網路模式 - fallback - 就是沒設定 datasource,包括 metadata 和 userdata - 當我們想要測試和 datasource 無關的 instance 這個技巧很好用 - 相當於設定 dhcp on eth0 - None - 通常會放在 datasource 的最後,所以說當所有 datasource 都不符合時,這個選項就會符合 - `instance id`: `iid-datasource-none` #### 如果這是第一次啟動,就會清除所有網路設定,並按照 userdata 的網路設定設定網路,包括保持舊的 mac 地址,或是舊的 device 名稱。 - 確認 instance id 來確定這是不是第一次啟動 - 網路在這個 stage 需要被 block,任何過時(前一次 boot 產生的設定)的網路設定都可能對以下功能造成影響 - DCHP - broadcast of an old hostname :::info datasource ( local ) 就是指說透過本機資訊就可以知道自己是甚麼 datasource 的 datasource ::: ### Network ![](https://i.imgur.com/yXa0ueH.jpg) :::info vendordata 和 vendordata2 的原因應該是 openstack 的 vendor會有兩個部分,一個是 nova , 另一個就是提供的維護者了。 ref: https://docs.openstack.org/nova/rocky/user/vendordata.html ::: ::: info 之前已經存過 why 要分成 user-data 和 vendor data 儲存可能是為了日後 debug 方便 ::: #### 簡表: | 項目 | 敘述 | |-----------------|---------------------------------------------------| | systemd service | cloud-init.service | | 執行條件 | 在 local statge 和 local statge 設定的網路都設定好之後 | | 需要阻止載入的設定 | nothing(並把剩餘得 boot 流程都跑完) | | modules | *cloud_init_modules* in `/etc/cloud/cloud.cfg` | 在上個階段網路設定好了之後就開始執行下面的動作 1. **載入所有設定** 2. **初始化 datasource** - 下載 userdata 裡 include 的所有 config,包括: - userdata 和 vendor data - yaml 檔( 例如最強大的 cloud-config ) - shell scripts - 其他檔案 - metadata - 名稱 - instance id - display name - 所有 `#include` `#include-once`(recursive) 包括 http get 到的檔案 - 解壓縮所有 metadata, userdata, vendor data 3. **啟動可以找到的 part-handler** 4. **因為 userdata 和 handler 都準備好了** 5. **這個 stage 執行的 module ( cloud_init_modules )** - migrator - seed_random - bootcmd - write-files - growpart - resizefs - disk_setup : 分割硬碟 - mounts : 寫入檔案到 `/etc/fstab` - set_hostname - update_hostname - update_etc_hosts - ca-certs - rsyslog - users-groups - ssh :::info - 定義的 moudules 會再 `/etc/cloud/cloud.cfg` 被聲明 ![](https://i.imgur.com/8ZG70D5.png) - 定義的 packages `/usr/lib/python3/dist-packages/cloudinit/config` 會被放在這裡 ![](https://i.imgur.com/FQdsJtm.png) <br> 自己新增想要的 moudles 看來是可行的 <br> > Talk is cheap, show me the code! [name=BT] ::: ::: info 這個 statge 執行的 module,都無法在 local statge 執行前被執行 例如:分割需要 userdata,但在沒有配置網路(local statge)的情況下,無法得到 userdata,所以無法分割和掛載 ::: <!-- > FIXME: 環境 flow(分支) 架構 --> ### Config ![](https://i.imgur.com/kSHjNnT.jpg) #### 簡表 | 項目 | 敘述 | |-----------------|----------------------------------------------| | systemd service | cloud-config.service | | 執行條件 | after network | | 需要阻止載入的設定 | nothing | | modules | cloud_config_modules in /etc/cloud/cloud.cfg | - 這個 statge 執行的 module 通常是和其他 stage 沒有前後關係的 moudle - emit_upstart - snap - ssh-import-id - locale - set-passwords - grub-dpkg - apt-pipelining - apt-configure - ubuntu-advantage - ntp - timezone - disable-ec2-metadata - runcmd : 執行 bash 指令 - byobu ### Final ![](https://i.imgur.com/VH5QEH5.jpg) #### 簡表: | 項目 | 敘述 | |-----------------|------------------------------------------------| | systemd service | cloud-final.service | | 執行條件 | 最後一個 stage, 性質類似 `rc.local` | | 需要阻止載入的設定 | nothing | | modules | cloud_final_modules in /etc/cloud/cloud.cfg | :::info 要開機讓 linux 主動啟動程式主要有兩種 1. rc.local - 類似 cmd line 的配置方式 2. systemd 的 service [rc.local](https://www.freebsd.org/cgi/man.cgi?rc.local) ::: #### 所有在登入過後應該執行的部分應該都放在這裡 - cloud_final_modules - package-update-upgrade-install - fan - landscape - lxd - ubuntu-drivers - puppet - chef - mcollective - salt-minion - reset_rmc - refresh_rmc_and_interface - rightscale_userdata - scripts-vendor - scripts-per-once - 放在 `/var/lib/cloud/scripts/per-once` 資料夾內的 script 只會執行一次 - 可以使用 `write-files` module 增加 shell script - scripts-per-boot - 放在 `/var/lib/cloud/scripts/perboot` 資料夾內的 script 每次 reboot 都會跑一次 - 可以使用 `write-files` module 增加 shell script - scripts-per-instance - 放在 `/var/lib/cloud/scripts/per-instance`資料夾內的 script 在第一次 launch 的時候才會執行,但如果對 instance 做重大更改, instance-id 更改時就會重新在執行一次 - 可以使用 `write-files` module 增加 shell script - scripts-user - ssh-authkey-fingerprints - keys-to-console - phone-home - final-message - power-state-change - 可以和 cloud-init 初始化過中得程式碼分開 - 使用 cloud-init status --wait 等待到 cloud-init 初始化流程完成 - 假設自己 build 的 image 有使用其他初始化的手段,可能造成問題 - 使用 provisioner build 後的 image , 會透過 packer 去安裝 packages - 如果在 apt install 時, source.list 被改變,可能就會找不到 package 安裝位置 ```bash= #!/bin/bash # waiting cloud-init to update /usr/bin/cloud-init status --wait sudo apt update -y sudo apt install -y chrony ``` [provisioner packer error](https://shazi.info/packer-build-ec2-ami-%E6%99%82%E7%AD%89%E5%BE%85-cloud-init-%E5%85%88%E5%AE%8C%E6%88%90/) :::info 如何實現等待,就是每 0.25 秒偵測一次是否完成初始化,結束就 return; vim `/usr/lib/python3/dist-packages/cloudinit/cmd/status.py` ![](https://i.imgur.com/Ujtwk8g.png) ::: ::: info 因為要訂製開機之後的行動,所以不一定所有的 distribution 都有支援 cloud-init - 有支援的 distorbution * Alpine Linux * ArchLinux * Debian * Fedora * FreeBSD * Gentoo Linux * NetBSD * OpenBSD * RHEL/CentOS * SLES/openSUSE * Ubuntu ::: ### 到底能處理那些動作? * 執行指令: 執行一連串的指令,並且可以顯示在 output log. * 設定 ssh key: 增加 ~/.ssh/authorized_keys * 設定 package: 更新 source list 或安裝 package * 設定網路: 更新 /etc/hosts, host name 等等的 * 寫入檔案: 寫檔到硬碟空間 * 增加 repository: 增加 apt 或 rpm 的 repository * 新增 user 和 group: 新增 user 和 group, 同時設定他們的 properity. * 升級: 升級 package. * 重啟: 重新啟動 instance * ...more [capibility](https://cloudinit.readthedocs.io/en/latest/topics/modules.html) ## debug - `/run/cloud-init/status.json` - 有 boot 的時間 - 可以看看那一個 stage 可以縮短,加速開機時間 - 每一步有沒有出現 error - ![](https://i.imgur.com/XE7VxlN.png) - `/run/cloud-init/result.json` - datasource error 的位置, troubleshooting 的好地方 - `/var/log/cloud-init-output.log` - print generic output - 印出所有 cloud-init 做的事情,和做的同時 console 跑出的東西 - 同樣是個 troubleshooting 的好地方