那时你以为你了解 Y(A)ML 😵
大家好,
感谢您阅读这篇关于 YAML 文件的文章。今天我们将制作一个关于 YAML 配置文件的轻量级教程。我们将了解 YAML 是什么,如何开始使用它,以及 YAML 文件在哪些场合下使用,但可能忽略了一些细微的差别。
Y(A)ML 是一种数据序列化语言,它是 JSON(JavaScript 对象表示法)的严格超集。它是一种面向数据的结构化语言,可用作不同软件应用程序的输入格式。我们可以推断,该语言最终由键值对组成。YML 的目标是以简洁明了的方式提高可读性。
我们经常通过与 GUI 界面交互来使用可用的工具,但我们没有意识到,在底层,只有一个 YAML 文件,它存储着我们针对特定任务的个人配置。今天,我们将在这里学习几个示例,并学习这门语言。
YAML 主要有两种类型:标量 (Scalar)和集合 (Collection)。高中物理课上我们学到标量只包含描述大小的值,YAML 也同样如此。这意味着我们只能使用一个唯一的键来保存值,如果在文件中再次使用同一个键,它将覆盖之前设置的值。例如,如果我们想将变量(键)“NAME”声明为值“Joey”,那么这个变量(键本身)就是唯一的,我们可以在文件中全局使用它。
# key : value
NAME: Joey
如果我们不小心,再次将该变量声明为不同的值,例如“Chandler”,那么最后一个实例将覆盖原始值。
NAME: Joey
# ...
# other
# yaml
# configurations
NAME: Chandler
# this line will be the only source of truth when the file is evaluated, thus overriding every instance of the key NAME beforehand
集合基本相同,它也由键值对组成,但一个键可以包含多个值。例如,一个姓名列表。
# list
NAMES: ["Joey", "Chandler", "Ross", "Phoebe", "Rachel", "Monica"]
描述相同 NAMES 列表或序列的另一种方法是
# list or sequence
NAMES:
- "Joey"
- "Chandler"
- "Ross"
- "Phoebe"
- "Rachel"
- "Monica"
YAML 中的集合不仅可以用数组的形式描述,还可以用映射来描述。例如,如果我们想描述一个人的邮寄地址。现在我们先简单介绍一下。地址由街道名称、门牌号、城市、州和邮政编码组成。让我们看看如何将此地址转换为 YAML,我们将选择美国某家必胜客的地址。
# yaml object
address:
street_name: North Mathilda Avenue
street_number: 464
city: Sunnyvale
state: CA
zipcode: 94085
正如我们在这里看到的,我们有一个名为“address”的键,其中包含多个键值对。您需要注意缩进。当我们想要将多个键值对分组到一个逻辑容器(即父容器)下时,必须使用两个空格进行缩进,并且每个新行必须垂直对齐,否则 YAML 文件在准备执行时会抛出错误。
这种特殊的描述被称为“映射”。映射名称为“address”,它包含几条数据,这些数据通常以键值对的形式存在。你还可以注意到,这些值不仅可以是“字符串”类型,还可以是“数字”(整数、浮点数),也可以是布尔值。顺便说一下,对于字符串,引号是可选的。我们也可以定义一个日期变量,但需要注意的是,日期格式必须符合 ISO 8601 标准,例如:“yyyy-mm-dd hh:mm:ss:sss”。
# dates ISO 8601
some_date: 2018-30-09
some_datetime: 2020-10-01 09:10:30
由于我们知道 YAML 由键:值对组成并且是 JSON 的超集,因此我们能够以 json 样式描述映射对象。
# json style map object in YAML
person: { name: "Johnny", age: 35, single: true }
我不太喜欢混用这两种风格,因为说实话,我们有时会写很长的 YML 文件,而且根据我的经验,如果出错了,调试起来会非常麻烦。虽然你可以这样做,但这并不意味着你必须这样做。
到目前为止,我们讨论的是类型,并看到了一些比较简单的示例。让我们看一个可以开始复杂化的例子。在第一个例子中,我们将了解如何组合 Map 和 Collection。假设我想要表示一个人员列表,并将该列表表示为 Map 对象的 Collection。
people:
# method 1 - JSON style map object
- { name: Alex, age: 18, single: false }
# method 2 - YAML map object
- name: Eric
age: 19
single: true
# method 3 - another YAML map object, pay attention to the line break
-
name: "Sam"
age: 22
single: true
正如我们在此示例中所见,我们声明了一个名为“people”的变量(键),它包含多个格式相同的对象。我们还可以看到,我们声明每个映射对象的方式各不相同,并且我们使用了三种不同的方法来描述映射对象的相同格式,但它们在 YAML 中看起来都一样。需要指出的是,我们可以根据需要进行任意数量的嵌套。例如,如果 person 对象具有描述“爱好”的属性,我们可以添加它,从而创建一个包含列表的列表对象。让我们通过一个例子来看一下。我将使用前面的集合作为参考。
people:
- name: Tamara
age: 20
single: true
hobbies: [movies, sports, food]
- name: Julia
age: 25
single: false
hobbies:
- movies
- sports
- food
# pay attention to the nesting
-
name: Elaine
age: 29
single: false
hobbies:
- movies
- sports:
- swimming
- hiking
- dancing
- food
到目前为止,我们介绍了 YAML 的类型及其使用方法。现在,我们将了解 YAML 支持的一些功能。现在,我们将了解一下格式化。如果我们有一个键需要保存大量数据(例如特定对象的描述),则有两种方法可以对其进行格式化。我们将使用右箭头“>”或竖线“|”。它们之间的主要区别在于是否保留格式。右箭头“>”不会保留格式,而竖线“|”会保留格式。我们使用格式化的原因是为了让我们更容易阅读,YAML 会在后台将所有内容渲染到一行中。让我们看看它的实际效果。
# no formatting. the text is written in one line
car:
model: Toyota
make: 2021
description: "Awarded Green Car Journal's 2020 Green Car of the Year®, Corolla Hybrid even comes with an enhanced Hybrid Battery Warranty that lasts for 10 years from date of first use, or 150,000 miles, whichever comes first"
# chevron right '>' sign will not preserve the formatting; no need for quotes
car:
description: ">"
Awarded Green Car Journal's 2020 Green Car of the Year®,
Corolla Hybrid even comes with an enhanced Hybrid Battery Warranty that lasts for 10 years from date of first use,
or 150,000 miles, whichever comes first
model: Toyota
make: 2021
# pipe '|' sign will preserve the formatting; no need for quotes
car:
description: "|"
Awarded Green Car Journal's 2020 Green Car of the Year®,
Corolla Hybrid even comes with an enhanced Hybrid Battery Warranty that lasts for 10 years from date of first use,
or 150,000 miles, whichever comes first
model: Toyota
year: 2021
恭喜!您现在已经掌握了所有基础知识,可以像专业人士一样在日常工作中使用 YML 了。我们还有一个主题需要讨论,稍后我们会详细讨论。我想事先指出一些细微的差别。
YAML 还支持其他一些我们在本文中未讨论的功能,我之所以选择不讨论这些功能,是因为这些功能的用例非常有限,需要证明其合理性。例如,用于显式类型的标签、元组、将键设置为非字符串、段落等等。您可以在官方YAML 文档中了解更多信息。
如果您确实希望我演示一些示例,那么请告诉我,我将制作另一个简短的部分来重点介绍这些功能。
YAML 还有一个重要特性叫做锚点,我经常看到人们因为各种原因并不真正使用它。说实话,我真的不知道锚点有什么可怕的,而且我认为使用它们能给我们带来巨大的价值。锚点使我们能够复制配置或内容,甚至继承整个文件的属性。我们不仅可以复制一段配置,还可以对锚点中已经定义的特定键注入覆盖,从而使其非常灵活。我同意,如果你有一些小的或基本的配置文件,那么就没有理由使用它,但如果我们假设文件的内容会增长,那么设置锚点的额外工作是值得的。
我们使用“&”符号和“*”符号来处理锚点。
定义锚点的格式是:声明一个键,后跟锚点名称,锚点名称前面加上“&”符号,最后是值。
mykey: &myanchor myvalue
需要注意的是,键和锚点名称不必完全一致。当我们想要使用锚点时,需要将以“*”符号开头的锚点名称作为值赋给另一个键。
anotherkey: *myanchor
示例 - YAML 锚点 1
name: &actor Neo
movie_charachter: *actor # movie_charachter will hold the value Neo
正如我们在这个简单示例中所见,这并非我们真正应该使用锚点的原因或时机。我们并非寻求锚点的简单实现。我通常在需要配置具有多个属性的对象或键值对时使用它们,这些属性或键值对不应在文件中任何需要复制实例的地方发生变化。对于复杂的键值对,我们使用锚点的方式是使用双左箭头“<<”符号,后跟锚点。
示例 - YAML 锚点 2
# global car object that we want to use across
car: &base_car
year: 2021
make: Toyota
model: Corolla
color: Grey
# reuse the car object without changing anything
corolla:
<<: *base_car
# reuse the car object and override one of the properties
runx:
<<: *base_car
model: runx
# reuse the car object and override several of the properties
prius:
<<: *base_car
model: prius
color: Red
# reuse the car object, override property and add additional that doesn't exist in the original anchor
camry:
<<: *base_car
model: camry
seats: 5
正如我们在本例中看到的,我们声明了一个锚点,在 YAML 文件中的不同位置使用了它,并对其进行了自定义。请注意,自定义也适用于嵌套属性。只需阅读我们讨论嵌套的部分并实现它即可。无需重新编写。每个地图对象看起来都与添加了调整后的锚点相同。
让我们来谈谈在日常工作中最有可能遇到 YAML 文件配置的地方。作为开发人员和/或 DevOps 工程师,我们在使用 Docker(特别是 Docker Compose)以及 CI/CD 流水线时经常会遇到 YAML 配置。这两个示例是最常见的。
示例 - YAML Docker Compose
在这个例子中,我们将查看一个用于本地开发环境的简单 docker compose 配置文件
# docker compose example
# simple key:value pair
version: "3"
# complex Map object with nested map objects, each nested object represents a service in docker compose
services:
# service Map object
redis_sentinel:
image: redis:alpine
volumes:
- sentinel_data:/data
# service Map object
redis_worker:
image: redis:alpine
environment:
- ALLOW_EMPTY_PASSWORD=yes
volumes:
- worker_data:/data
# another complex Map object with nested map objects. volumes used for persistent storage in docker. docker containers are ephemeral which means that they are not designed to run forever, especially in local dev environment and that is why we need to create a volume and bind the local host's file system into the docker container's file system
volumes:
# service Map object
sentinel_data:
driver: local
# service Map object
worker_data:
driver: local
正如我们在示例中看到的,我们有一个 YAML 配置文件的常见用例,该文件以重复的方式编写。我相信您有足够的信心尝试自己重写这个 YAML 配置文件。让我们试一试
# docker compose example
redis_service: &base_redis
image: redis
volume: null
volumes:
sentinel_data:
driver: local
worker_data:
driver: local
version: "3"
services:
sentinal:
<<: *base_redis
volume:
- sentinal_data:/data
worker:
<<: *base_redis
volume:
- worker_data:/data
environment:
- ALLOW_EMPTY_PASSWORD=yes
您可以看到,我没有修改卷的全局配置,因为它是 Docker 特有的,通常情况下您不希望将其设置为动态的。然而,我们可以看到重写并没有带来太大的影响,但我们首先需要记住,这是一个非常基本的 Docker Compose 配置文件,如果您在“服务”下添加另一个“服务”,您将会看到影响。此外,base_redis 锚点非常轻量。想象一下,我们有 20 个属性,并且它们之间有嵌套属性,我们的文件会是什么样子。
示例 - YAML - CI
# Travis ci example
language: node_js
node_js:
- node
env:
global:
- PATH=$HOME/.local/bin:$PATH
before_install:
- pyenv global 3.7.1
- pip install -U pip
- pip install awscli
script:
- yarn build
- echo "Commit sha - $TRAVIS_COMMIT"
- mkdir -p dist/@myapp/$TRAVIS_COMMIT
- mv dist/*.* dist/@myapp/$TRAVIS_COMMIT/
deploy:
provider: s3
access_key_id: "$AWS_ACCESS_KEY_ID"
secret_access_key: "$AWS_SECRET_ACCESS_KEY"
bucket: "my_project_bucket"
region: "us-west-2"
cache-control: "max-age=31536000"
acl: "public_read"
local_dir: dist
skip_cleanup: true
on:
branch: master
after_deploy:
- chmod +x after_deploy_script.sh
- "./after_deploy_script.sh"
如你所见,此文件中的配置一目了然,并且模式一致。我们使用键值对,并且大多数配置都是复杂的 Map 对象。
好了,各位!这就是你需要了解的关于 YAML 的全部内容。从现在开始,你在处理 YAML 文件配置时应该会更加自信,甚至有机会改进现有的文件。
我有时会使用一个很酷的工具来验证我处理的 YAML 文件,它会检查拼写和配置,这样你就可以确保你没有错过任何东西,如果你错过了什么,那么就会抛出一个错误😄
顺便说一句,如果你问自己 YAML 代表什么,它是另一种标记语言。😄😄😄
请继续关注下一个点
赞、订阅、评论等等...
再见