YAML 常見的問題

Hacker News Daily 上看到「The yaml document from hell」這篇在抱怨 YAML 的問題,而 Hacker News 上對應的討論在「The Yaml document from hell (ruudvanasseldonk.com)」這邊。

翻了一下我之前也有提到好幾次不同來源的抱怨:「YAML 的地雷 (2014)」、「YAML 的痛點 (2019)」、「YAML 的問題 (挪威問題) (2021)」。

這篇提到的東西還是類似之前提到的,但整理的蠻不錯的?他給了一個看起來蠻正常的 YAML,然後裡面全部都是地雷,你可以看他的說明知道是什麼問題。

不過他提出來的問題都是可以加上 double quote 來避開,但把這個方式當作 common practice 用 YAML 會變得很痛苦。

不過市場上還沒有能取代的東西,只能先繼續邊用邊罵了,看了一下 Hacker News 上的留言,簡單一點的東西 (只是要放幾個值的) 大家都覺得 INI 還可以拿來用用...

Ansible 的爭論

前幾天在 Hacker News Daily 上看到「Five Ansible Techniques I Wish I’d Known Earlier」這篇,裡面提到了一些 Ansible 的用法還蠻有用的,算是開始用 Ansible 後應該都會有幫助的用法... 不過 Hacker News 上的討論「Ansible Techniques I Wish I’d Known Earlier (zwischenzugs.com)」比較精彩...

目前在頂端的留言對 Ansible 幹到不行,尤其是那個 YAML 格式:

Ansible is abysmal. I don't know why anyone still chooses it. It's a mess of yaml and what feels like a million yaml files that is always extremely hard to follow. Honestly writing some python, or bash is a lot easier to follow, read, and understand. The only thing it has going for it is the inventory system. I wish ansible would die already.

然後講到 bash 與 python 之類的工具時有人提到 idempotent:

>bash and python
Neither of those of idempotent.

馬上就有人幹勦,大多數人在寫 Ansible playbook 時根本沒人在注意 idempotent,而且一堆 shell script 的東西被塞進 YAML 只能說痛苦 XDDD

Most of the ansible roles I come across written by my team are not idempotent either, its a huge lie that Ansible is idempotent. Its idempotent if you put the effort into make it be but if I see tons of shell or command module invocations without prerequisite checks to see if the work should be done. Most devs I see using Ansible treat it like a shell script written in YAML and to that purpose it sucks.

我自己目前會挑 Ansible 主要還是因為 server 不需要另外裝軟體,是個 production 為導向的設計,再更大的時候就要想一下要怎麼繼續搞下去了...

YAML 的問題 (挪威問題)

Hacker News 首頁上看到的,YAML 寫多的人都遇過類似的問題:「The Norway Problem - why StrictYAML refuses to do implicit typing and so should you」,對應的討論「The Norway Problem (hitchdev.com)」也可以看一下。

第一個「經典」是字串不需要包起來就很容易出事,這邊提到的例子是因為保留字而中槍,挪威的簡碼 NO 變成 False 了:

countries:
- GB
- IE
- FR
- DE
- NO
>>> from pyyaml import load
>>> load(the_configuration)
{'countries': ['GB', 'IE', 'FR', 'DE', False]}

同樣的是字串問題,「看起來」是數字的就會變成數字:

python: 3.5.3
postgres: 9.3

然後還是字串,人名遇到保留字:

first name: Christopher
surname: Null

這種問題都是碰過一次學一次...

作者另外提到的 StrictYAML 改變了 YAML 規格,我會看看就好,能用 JSON 也許還是會偏好先用 JSON,不是完全解決,但踩雷的機率會少很多。

改 Open Distro for Elasticsearch 預設密碼的方式

AWS 弄出來的 Open Distro for Elasticsearch 因為內建了安全性的功能 (參考「AWS 對 Elastic Stack 實作免費的開源版本 Open Distro for Elasticsearch」),應該是目前新架設 Elasticsearch 的首選。

不過裝好後預設有五個帳號,但從 Open Distro 的 Kibana 介面無法修改改其中兩個使用者的密碼 (adminkibanaserver),要修改密碼發現得花不少功夫,不知道為什麼要這樣設計 :/

這邊講的是透過 RPM (以及 deb) 的方式的修改方式,如果是 Docker 的方式請參考後面提到在 AWS blog 上的文章:「Change your Admin Passwords in Open Distro for Elasticsearch」。

首先先透過 hash.sh 產生 bcrypt 的 hash,像是這樣 (輸入 password 當密碼):

bash /usr/share/elasticsearch/plugins/opendistro_security/tools/hash.sh
WARNING: JAVA_HOME not set, will use /usr/bin/java
[Password:]
$2y$12$QchkpgY8y/.0TL7wruWq4egBDrlpIaURiBYKoZD50G/twdylgwuhG

然後修改 /usr/share/elasticsearch/plugins/opendistro_security/securityconfig/internal_users.yml 檔案裡面的值,順便改掉 readonly 的部分。

接下來是把這個 internal_users.yml 檔案的設定更新到 Elasticsearch 裡。由於這邊需要讀 /etc/elasticsearch/ 的東西,所以偷懶用 root 跑:

sudo bash /usr/share/elasticsearch/plugins/opendistro_security/tools/securityadmin.sh -cd ../securityconfig/ -icl -nhnv -cacert /etc/elasticsearch/root-ca.pem -cert /etc/elasticsearch/kirk.pem -key /etc/elasticsearch/kirk-key.pem

做完後可能要重跑 Elasticsearch 與 Kibana:

sudo service elasticsearch restart
sudo service kibana restart

或是重開機... 順便測試看看重開後有沒有生效。理論上修改完成後,就是用新的帳號密碼連到 Kibana。

上面的方法是參考了「Default Password Reset」(先找到這篇) 與「change admin password」(後來在 AWS blog 的文章上發現的 GitHub issue 連結) 這邊的資訊。

官方的說明文件則是在寫這篇文章時才找到的,平常搜尋時不太會出現:「Apply configuration changes」。

YAML 的痛點

Changelog 上看到「In defense of YAML」這篇講 YAML 的問題,裡面是引用「In Defense of YAML」這篇文章。

未必全盤接受文章裡面的說法,但裡面提到的兩個點的確是痛點,第一個是空白 (或者說 indent),第二格式特殊語法。這兩個是用 YAML 時都很頭痛的問題:

Whitespace is a minefield. Its syntax is surprisingly complex.

就像 JavaScript 的 == 一樣 (我指的是之前寫的「JavaScript 的 == 條列式比較」這篇),你可以把定義背下來,但你會覺得沒什麼道理可言而有種無奈的感覺...

文章裡也有提到 JSON 內沒有 comment 的設計的確是用起來比較無奈的地方...

YAML 裡放多行區塊的方式

因為在寫 ansible 的 playbook,有些地方想要偷懶,把檔案放進在 playbook 本身...

網路上搜了一下找到「In YAML, how do I break a string over multiple lines?」這篇,最常用的應該是 ||- 以及 |+ 了,用這三招塞檔案應該是夠用...

裡面的範例拿 JSON 的格式來解釋還蠻有趣的,代表 JSON 真的很好懂...

GitHub 也自己搞了一套管理多家 DNS 的程式...

StackOverflow 團隊發表完自己開發管理 DNS 的程式後 (參考「StackOverflow 對於多 DNS 商的同步方式...」),GitHub 也來參一腳:「Enabling DNS split authority with OctoDNS」。

可以看到 GitHub 用了兩家的系統 (AWSRoute 53Dyn 的服務):

;; AUTHORITY SECTION:
github.com.             172800  IN      NS      ns1.p16.dynect.net.
github.com.             172800  IN      NS      ns3.p16.dynect.net.
github.com.             172800  IN      NS      ns2.p16.dynect.net.
github.com.             172800  IN      NS      ns4.p16.dynect.net.
github.com.             172800  IN      NS      ns-520.awsdns-01.net.
github.com.             172800  IN      NS      ns-421.awsdns-52.com.
github.com.             172800  IN      NS      ns-1707.awsdns-21.co.uk.
github.com.             172800  IN      NS      ns-1283.awsdns-32.org.

GitHub 的 OctoDNS 用 YAML 管理:

octodns:
  type: A
  values:
    - 1.2.3.4
    - 1.2.3.5
zones:
  github.com.:
    sources:
      - config
    targets:
      - dyn
      - route53

有種當初 Dyn 被打趴後大家硬是想個解法的產物... @_@

Hjson:the Human JSON

前幾天看到「Hjson, the Human JSON」這東西,想要在 JSON 上面提出拓展,讓人更好維護。

有幾個設計是大家已經想很久了。

首先是允許註解:

{
  # specify rate in requests/second
  "rate": 1000
}

再來是允許 ending trailing comma,這點在新的 JavaScript Engine 裡面是允許的,但在 JSON 規格裡是不允許的,對於 copy-paste 時就得很小心有沒有中獎:

{
  one: 1,
  two: 2,
}

另外幾個特點就還好。

object 的 key 沒有特殊情況時可以省略 double quote:

{
  key: "value"
}

甚至 value 是 single line 時也可以省略:

{
  text: look ma, no quotes!
}

而且當沒有 double quote 時不需要處理 escape 問題:

{
  path: c:\windows
  inject: <div class="important"></div>

  # escapes work inside quotes
  escape: "c:\\windows"
}

然後逗點可以省略,給的範例也突顯出對腦袋不直覺的問題 (ambiguous),這邊的 1 是 integer 還是 string?

{
  one: 1
  two: 2
}

多行,用 ''' 應該是借用了 Python 的想法?

{
  haiku:
    '''
    JSON I love you.
    But strangled is my data.
    This, so much better.
    '''
}

規格後面有提到 syntax,可以看到定義。

Hjson 算是一個開始吧,YAML 的設計需要極長的 training 時間才能正確使用,不知道 SaltStack 會不會有人馬上寫 adapter 出來接?(因為 SaltStack 已經可以接 JSON 與 YAML,只要有人把該接的接上去就可以了)

YAML 的地雷

因為碰 SaltStack,而官方建議用的格式是 YAML (雖然也支援 JSON,但文件幾乎都是 YAML),所以被迫要學一堆奇怪的 YAML hack,在官方文件上甚至寫了一篇「YAML Idiosyncrasies」讓大家參考,用 Idiosyncrasies 這個詞彙比較中性,但需要專文來寫就可以想像 YAML 有多 !@#$%^...

然後文章裡面也發現 SaltStack 在亂搞,於是就快起笑了...

首先是建議 indent 為 2 spaces,另外禁用 tab,這些到是沒什麼好抱怨的。但 dict 的設計就讓人崩潰,像是這樣的結構:

foo:
  - bar:
    baz1: abc
    baz2: def

你以為對應的 JSON 是:

{
  "foo": {
    "bar": {
      "baz1": "abc",
      "baz2": "def"
    }
  }
}

但實際對應的 JSON 中,bar、baz1、baz2 視同一層:

{
  "foo": {
    "bar": null,
    "baz1": "abc",
    "baz2": "def"
  }
}

因為其實對應的 YAML 是:

foo:
  - bar:
  - baz1: abc
  - baz2: def

你就不能把最上面的 YAML 定義成 syntax error 嗎... =_=

接下是 SaltStack 的惡搞時間,因為 YAML parser 會把 644 當作數字傳進去,所以這樣的設定:

/etc/vimrc:
  file:
    - managed
    - source: salt://edit/vimrc
    - mode: 644

SaltStack 會收到 644 (十進位),而如果你寫成 0644 時,就會被讀成八進位,也就是 420 (十進位):

/etc/vimrc:
  file:
    - managed
    - source: salt://edit/vimrc
    - mode: 0644

我覺得後面這個是比較正確的寫法,所以應該要會動,但 SaltStack 對這部份 workaround,會變成 chmod 420 /etc/vimrc,然後就噴飯了...

另外 2013_01_12 這種字串會被解讀成 20130102 (十進位),這會不會太歡樂...

反正用下去後應該會再踩更多地雷,繼續看下去吧...

SaltStack 的 Masterless 模式

最近在試 SaltStack,先從 Masterless 模式開始玩,可以拿來練習寫 SaltStack 專門的 sls 檔。相關的文件可以參考「Standalone Minion」這篇。

我是裝 Ubuntu 14.04.1 LTS,然後用 ppa 裝 SaltStack 最新版,避免與與官方的文件差異太大:

# apt-add-repository ppa:saltstack/salt
# apt-get update
# apt-get install salt-minion

然後建立 /srv/salt 後就可以在這個目錄下面開始做事。這個目錄是 SaltStack 的預設值 (可以參考 /etc/salt 下面的檔案),所以不需要另外再設定:

# mkdir /srv/salt
# cd /srv/salt

SaltStack 讀取的起點預設是 top.sls,這個檔案預設的格式是 YAML

base:
    '*':
        - default

然後就可以寫 default.sls

most:
  pkg:
    - installed

locale:
  cmd.run:
    - name: locale-gen zh_TW.UTF-8

然後在機器上呼叫 salt-call 執行:

# salt-call --local state.highstate

或是開 debug 訊息:

# salt-call --local -l debug state.highstate

這樣就可以看到各種輸出結果了。這樣應該就會看到 most 被裝起來,另外 zh_TW.UTF-8 的 locale 應該也會生出來。