This repository contains the saltstack prototype that implements the roles_and_profiles structure.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

744 lines
20 KiB

  1. // vim: set filetype=asciidoc:
  2. = Terms and Structure of the SaltStack Repository
  3. This document describes different terms used in this repository and gives an
  4. overview of the structure of the repository.
  5. Version:: 1.1
  6. == Overview
  7. The goals of this structure are the following:
  8. * Make it easy to reuse existing code (states and pillars)
  9. * Make it easy to add new and extend existing code
  10. * Make it easy to reason and understand what gets executed on the minion
  11. These goals are achieved by doing the following:
  12. * Defining an structure through code (topfile)
  13. * Using folder based matching instead of complicated topfile matching
  14. * Introducing the <<profile,profile>> abstraction layer
  15. ** Describe dependencies between <<formulas,formula>>
  16. ** Reuse and extend existing <<formulas,formula>>
  17. * Moving pillars from the <<global,global>> space to the <<role,role>> space
  18. This structure is inspired by the puppet structure "Roles and
  19. Profiles"footnote:[https://docs.puppet.com/pe/2017.1/r_n_p_intro.html].
  20. == Terms
  21. === State
  22. Examples:: `formula.nginx.pkgs`, `profile.pkgng_cacher.cachedir`
  23. The smallest part of SaltStack where we define what should be executed on the
  24. minion. These can contain installing packages, rolling out configuration files
  25. or starting and enabling services.
  26. See also the saltstack documenation about this: link:https://docs.saltstack.com/en/latest/ref/states/writing.html[Saltstack - State Modules]
  27. ==== Examples
  28. A typical state would look like this:
  29. .Template state for rolling out a configuration file
  30. [source,yaml]
  31. ----
  32. formula.template.config:
  33. file.managed:
  34. - name: '{{ lookup.config.name }}'
  35. - source: 'salt://{{ salt['file.join'](tpldir, "files", "config_template") }}'
  36. - mode: 0644
  37. - template: 'jinja'
  38. - require:
  39. - pkg: 'formula.template.pkg'
  40. # template variables
  41. - context:
  42. options: {{ options }}
  43. ----
  44. === Formula
  45. Examples:: `nginx`, `apache`, `haproxy`, `php`
  46. Formulas are a collection of <<state,states>>. They describe how to install and
  47. manage a single application or service.
  48. For example a formula can take care of the following:
  49. * Install the right package depending on the operating system
  50. * Decide where to put the configuration file if needed
  51. * Provide reasonable defaults for the configuration if possible
  52. * Provide a configuration file template that can easily be extended through
  53. <<pillars>>
  54. * Start and enable services that are associated with the software
  55. * Restart or reload services if certain conditions are met (change in
  56. configuration file)
  57. WARNING: If you need to have a dependency between two formulas you should write
  58. a <<profile>>!
  59. ==== File and folder structure
  60. A formula normally contains of these files and folders:
  61. `init.sls`:: Entry point for the formula that is used by salt. Here you can
  62. write your <<states,states>>.
  63. `map.jinja`:: Provides operating system specific defaults and general defaults
  64. that can be overwritten via <<pillars,pillars>>.
  65. `pillar.example`:: Contains example pillar options for the formula.
  66. `files`:: Contains all files that are used by the template. For example the
  67. configuration template.
  68. `README.md`:: Short description about what the formula is doing.
  69. ==== Examples
  70. Contents of the `template` formula.
  71. .`init.sls`
  72. [source,yaml]
  73. ----
  74. {#
  75. import the lookup and options map from the map.jinja file.
  76. tpldir is a jinja variable that gives the current folder so make it easier to
  77. move states around.
  78. #}
  79. {%- from salt['file.normpath'](tpldir+"/map.jinja") import lookup -%}
  80. {%- from salt['file.normpath'](tpldir+"/map.jinja") import options -%}
  81. # Install packages that are defined in the lookup map in the `map.jinja` file
  82. formula.template.pkg:
  83. pkg.installed:
  84. - pkgs: {{ lookup.pkgs }}
  85. # Deploy the configuration template from `files/config_template` which renders
  86. # the content of the contens map from `map.jinja` as YAML.
  87. formula.template.config:
  88. file.managed:
  89. - name: '{{ lookup.config.name }}'
  90. - source: 'salt://{{ salt['file.join'](tpldir, "files", "config_template") }}'
  91. - mode: 0644
  92. - template: 'jinja'
  93. - require:
  94. - pkg: 'formula.template.pkg'
  95. # template variables
  96. - context:
  97. options: {{ options }}
  98. # Make sure that the service specified in the lookup map from `map.jinja` is
  99. # running and enabled. Also make sure to restart the service if the
  100. # configuration has changed and restart is enabled.
  101. formula.template.service:
  102. service.running:
  103. - name: '{{ lookup.service.name }}'
  104. {% if lookup.service.restart %}
  105. - restart: True
  106. {% endif %}
  107. - enable: True
  108. - require:
  109. - pkg: 'formula.template.pkg'
  110. - file: 'formula.template.config'
  111. - watch:
  112. - file: 'formula.template.config'
  113. ----
  114. .`map.jinja`
  115. [source,jinja]
  116. ----
  117. {# lookup holds operating system specific options the lookup values can be
  118. overwritten by pillars in the lookup key #}
  119. {% set lookup = salt['grains.filter_by'](
  120. {
  121. 'FreeBSD': {
  122. 'pkgs': [
  123. 'no_package_like_this',
  124. 'another_package',
  125. ],
  126. 'config': {
  127. 'name': '/usr/local/etc/template.conf',
  128. },
  129. 'service': {
  130. 'name': 'template_service',
  131. 'restart': True
  132. },
  133. },
  134. 'Debian': {
  135. 'pkgs': [
  136. 'no_package_like_this',
  137. 'another_package',
  138. ],
  139. 'config': {
  140. 'name': '/etc/template.conf',
  141. },
  142. 'service': {
  143. 'name': 'template_service',
  144. 'restart': True
  145. },
  146. },
  147. 'RedHat': {
  148. 'pkgs': [
  149. 'no_package_like_this',
  150. 'another_package',
  151. ],
  152. 'config': {
  153. 'name': '/etc/template.conf',
  154. },
  155. 'service': {
  156. 'name': 'template_service',
  157. 'restart': True
  158. },
  159. },
  160. },
  161. grain='os_family',
  162. merge=salt['pillar.get']('formula.template:lookup'),
  163. ) %}
  164. {# defaults hold default values for the options that can be overwritten by the options/pillars #}
  165. {% set defaults = {
  166. } %}
  167. {# options hold the pillar information for the formula and are defined through a pillar #}
  168. {% set options = salt['pillar.get']('formula.template',
  169. default=defaults,
  170. merge=True) %}
  171. ----
  172. .`pillar.example`
  173. [source,yaml]
  174. ----
  175. formula.template:
  176. key: 'value'
  177. map:
  178. key: 'value'
  179. second_key: 'second_value'
  180. lists:
  181. - 'this is the first element'
  182. - 'this is the second element'
  183. ----
  184. .`README.md`
  185. [source,md]
  186. ----
  187. This is a template for a salt formula that can be used as a base for developing
  188. a new formula.
  189. ----
  190. .`files/config_template`
  191. [source,jinja]
  192. ----
  193. {{ options | yaml(False) }}
  194. ----
  195. === Profile
  196. Examples:: `trivago_default`, `appserver_php_apache`, `nginx_ssl_terminator`
  197. Profiles are an abstraction over formulas. They serve multiple purposes:
  198. * Make bundles of formulas and profiles that are often used by multiple roles
  199. (see <<profile-code-trivago_default-code,profile trivago_default>>)
  200. * Gluing formulas together (see <<profile-code-appserver_php_apache-code,profile
  201. appserver_php_apache>>)
  202. * Making it easier to reuse and extend existing formulas (see
  203. <<profile-code-nginx_ssl_terminator-code,profile nginx_ssl_terminator>>
  204. To use profiles in these cases solves these problems:
  205. * Inter formula dependencies which make it hard to reuse formulas in different
  206. ways. For example if we would combine `php` and `apache` directly we would not
  207. have the ability to use `php` combined with `nginx` without also using `apache`.
  208. * Standardized way to extend or reuse formulas which is clearly communicated.
  209. ==== Examples
  210. ===== Profile `trivago_default`
  211. Profile `trivago_default` bundles multiple formulas and profiles together that
  212. are used by almost all roles used at trivago.
  213. .`default` `init.sls`
  214. [source,yaml]
  215. ----
  216. include:
  217. - 'formula.hosts'
  218. - 'profile.pkgrepo'
  219. - 'formula.default_packages'
  220. - 'formula.salt_minion'
  221. - 'formula.sudoers'
  222. - 'formula.nrpe'
  223. ----
  224. ===== Profile `appserver_php_apache`
  225. Profile `appserver_php_apache` reuses the formula `php` and `apache` and will
  226. automatically restart apache when the PHP configuration changes.
  227. .`appserver_php` `init.sls`
  228. [source,yaml]
  229. ----
  230. include:
  231. - 'formula.php'
  232. - 'formula.apache'
  233. extend:
  234. formula.apache.service:
  235. # autorestart apache when php config changes
  236. - watch:
  237. - file: 'formula.php.config'
  238. ----
  239. ===== Profile `nginx_ssl_terminator`
  240. .`nginx_ssl_terminator` `init.sls`
  241. [source,yaml]
  242. ----
  243. include:
  244. - 'formula.nginx'
  245. extend:
  246. formula.nginx.config:
  247. file.managed:
  248. - source: 'profile.ssl_terminator.files.config_template'
  249. ----
  250. === Role
  251. Examples:: `pricesearch_server`, `ssl_terminator`, `saltmaster_dev`
  252. Roles include <<profiles,profile>> and <<formulas,formula>> to describe the
  253. business function of a minion.
  254. A minion can only have one role at the same time and are used for the _matching_
  255. inside the <<states,states>> and <<pillars,pillars>> topfile.
  256. ==== Examples
  257. ===== Role `saltmaster_dev`
  258. `saltmaster_dev` deploys a saltmaster and multiple jails on a minion to make it
  259. easy to develope saltstack states and pillars.
  260. .Statefile for `saltmaster_dev`
  261. [source,yaml]
  262. ----
  263. include:
  264. - 'profile.default'
  265. - 'profile.pkgng_cacher'
  266. - 'formula.salt_master'
  267. - 'formula.salt_api'
  268. - 'profile.jailmaster'
  269. - 'formula.salt-compressor'
  270. ----
  271. .Pillarfile for `saltmaster_dev`
  272. [source,yaml]
  273. ----
  274. include:
  275. - 'preset.datacenter_defaults'
  276. - 'role.saltmaster_dev.base'
  277. ----
  278. === Environment
  279. Examples:: `dev`, `stage`, `prod`
  280. Environments define in what kind of _stage_ the minion is in. Is it a
  281. development machine or a production machine.
  282. This makes it easy to make small behavior changes that are desirable in a
  283. _development environment_ but not in a _production environment_.
  284. ==== Examples
  285. ===== Configcheck on rollout
  286. For example if you develop a new state. You have a configuration check which
  287. checks the validity of a configuration file before rolling it out. You also have
  288. a template that gets rendered depending on <<pillar,pillar>> options.
  289. You now want to see the output of the template after applying the pillar
  290. options. If you make an error the configuration check will catch that and not
  291. deploy the rendered configuration file to the machine. This makes it hard to
  292. debug and fix the problem.
  293. In a production environment this is a desirable thing as it prevents errors that
  294. could lead to downtime. In a development environment this is annoying as it
  295. prevents you to see the actual output of your configuration file.
  296. You can now define in your pillars that the configuration check should only be
  297. run in production environments but not in development environments.
  298. This makes it easier to develop new states but still have good error checks in
  299. production.
  300. In code it could look like this:
  301. .Haproxy formula config state
  302. [source,yaml]
  303. ----
  304. formula.haproxy.config:
  305. file.managed:
  306. - name: '{{ lookup.config.name }}'
  307. - source: 'salt://{{ salt['file.join'](tpldir, "files", "config_template") }}'
  308. {% if lookup.config.check %}
  309. - check_cmd: '{{ lookup.service.name }} -c -f'
  310. {% endif %}
  311. ----
  312. .Global dev environment pillar
  313. [source,yaml]
  314. ----
  315. formula.haproxy:
  316. lookup:
  317. config:
  318. check: False
  319. ----
  320. .Global prod environment pillar
  321. [source,yaml]
  322. ----
  323. formula.haproxy:
  324. lookup:
  325. config:
  326. check: True
  327. ----
  328. === Realm
  329. Examples:: `dus`, `dus.frontend`, `dus.backbone`, `sfo`, `hkg`,
  330. `eu.dus.office.deepgrey.thaller.saltmaster_dev`
  331. Realms describe the geographical or logical location of the minion. They are
  332. used to change settings of the minion depending on their surrounding. If a
  333. minion is in the _hkg datatacenter_ it needs different IPs for its DNS and NTP
  334. server then a minion in the _sfo datacenter_.
  335. Realms should be treated as a _logical_ environment but are usually encoded in a
  336. _geographical_ location. This is mostly to make it easier to understand where a
  337. server is and follows our usual naming structure.
  338. Realms are hierarchically structured and should be separated by a `.` (dot). So
  339. for example the realm `eu.dus` is a subrealm of the realm `eu`. For more
  340. information on how realms are used see the <<folders>> section under
  341. <<pillars>>.
  342. [CAUTION]
  343. ====
  344. Realms should *not* be used to to differentiate between _dev_ and
  345. _prod_ environments.
  346. If you have behavior changes use a different <<role,role>> or an
  347. <<environment,environment>>.
  348. They can be used to setup a development realm which brings for example
  349. different IPs or endpoints with it.
  350. ====
  351. [IMPORTANT]
  352. ====
  353. If you want to have different settings than an existing realm its a new realm!
  354. ====
  355. ==== Examples
  356. `dus`:: Used by the Düsseldorf datacenter
  357. `sfo`:: User by the San Francisco datacenter
  358. `trv-dus-dg`:: Used by the Deep Grey office in Düsseldorf
  359. == Structure
  360. ----
  361. saltstack
  362. ├── pillars
  363. │   ├── global
  364. │   │   ├── environment
  365. │   │   ├── id
  366. │   │   └── realm
  367. │   ├── preset
  368. │   └── role
  369. │   └── <role>
  370. │   ├── environment
  371. │   ├── id
  372. │   └── realm
  373. └── states
  374. ├── formula
  375. ├── profile
  376. └── role
  377. ----
  378. === States
  379. .States dependencies generated by `graph_states_dependencies`
  380. image::states_dependencies.svg[States dependencies]
  381. Folders:: `formula`, `profile`, `role`
  382. States have a relatively simple structure. They just match the <<role,role>>
  383. grain of the minion to the files and folders in the `role` folder.
  384. ----
  385. states
  386. ├── formula
  387. │   ├── default_packages
  388. │   ├── haproxy
  389. │   ├── hosts
  390. │   └── nginx
  391. ├── profile
  392. │   ├── loadbalancer_datacenter
  393. │   └── trivago_default
  394. └── role
  395. ├── loadbalancer_datacenter
  396. └── ssl_terminator
  397. ----
  398. For example if the role of the minion is `ssl_terminator` then
  399. `role.ssl_terminator` will be used.
  400. The role file then includes <<formula,formulas>> and <<profile,profiles>>:
  401. .Role file `role.ssl_terminator`
  402. [source,yaml]
  403. ----
  404. include:
  405. - 'profile.default'
  406. - 'profile.ssl_terminator'
  407. ----
  408. === Pillars
  409. Folders:: `global`, `preset`, `role`
  410. Pillars have a more complicated structure than <<states,states>>. They provide
  411. a structured configuration for the states. As they are structured it's easy to
  412. extend and overwrite them.
  413. [CAUTION]
  414. ====
  415. The behavior of the pillars can heavily depend on the configuration of the
  416. saltmaster. See
  417. link:https://docs.saltstack.com/en/latest/ref/configuration/master.html#pillar-merging-options[Pillar
  418. Merging Options] for more information.
  419. On our saltmaster's we usually set the following options:
  420. .Usual pillar settings for saltmaster
  421. [source,yaml]
  422. ----
  423. # Recursively merge pillar data
  424. pillar_source_merging_strategy: 'recurse'
  425. # Recursively merge lists by aggregating them instead of replacing them.
  426. pillar_merge_lists: True
  427. ----
  428. ====
  429. ==== Folders
  430. ===== Global
  431. IMPORTANT: Global pillar should be avoided as much as possible and should only
  432. be used when absolutely necessary. Not all minions need all pillars all the time
  433. and globals make it harder to determine where pillars come from If you can put
  434. the pillars you want to add under the same folder in your <<role,role>>.
  435. Examples:: `global.realm.dus.frontend`, `global.environment.dev`,
  436. `global.id.ssl-ter0-dus`
  437. Stores pillars that are used between multiple roles.
  438. Global provides three folders to match pillars to a minion:
  439. `environment`:: Will match against the <<environment,environment>> grain of the
  440. minion. Should only contain small behavior changes like not auto-restarting
  441. _apache_ in `prod` when the configuration file changes.
  442. `realm`:: Will match against the <<realm,realm>> grain of the minion. Contains
  443. information about the "surrounding" of the minion like _dns server_ or _kafka
  444. server_.
  445. +
  446. The realms are hierarchically structured with subfolders:
  447. +
  448. ----
  449. realm
  450. ├── eu
  451. │   ├── ams
  452. │   │   └── office
  453. │   └── dus
  454. │   ├── datacenter
  455. │   │   ├── backend
  456. │   │   └── frontend
  457. │   └── office
  458. │   └── deepgrey
  459. └── north_america
  460. ├── dca
  461. │   └── datacenter
  462. └── sfo
  463. └── datacenter
  464. ----
  465. +
  466. Pillars defined in the realm `eu` would be inherited by the realms `eu.ams` and
  467. `eu.dus`. Pillars defined in a lower level for example `eu.ams` will overwrite
  468. pillars inherited by `eu`.
  469. +
  470. For example if you have the following pillars defined:
  471. +
  472. .`eu`
  473. [source,yaml]
  474. ----
  475. formula.example:
  476. key1: 'value1'
  477. key2: 'value2'
  478. ----
  479. +
  480. .`eu.dus`
  481. [source,yaml]
  482. ----
  483. formula.example:
  484. key1: 'value3'
  485. ----
  486. +
  487. The resulting pillar for a minion in the `eu.dus` realm would be the following:
  488. +
  489. [source,yaml]
  490. ----
  491. formula.example
  492. key1: 'value3'
  493. key2: 'value2'
  494. ----
  495. `id`:: Will match against a specific minion ID.
  496. ===== Preset
  497. Examples:: `preset.datacenter_defaults`, `preset.dev_ssl_cert`
  498. Contains preset pillar files that are reusable between multiple roles.
  499. They make it easier to opt-in to pillars instead of having a default matching,
  500. but still having a way to share pillars between roles.
  501. They contain defaults we want to have on all minions. Good example are
  502. `nrpe` pillars which enable checks we want to have enabled on all minions.
  503. They also contain pillars that are useful to different minions but are not
  504. needed on all minions. For example the `dev_ssl_cert` contains a valid
  505. certificate that can be used for when applications get tested against HTTPs.
  506. They can be `include` files or their own pillar entry files.
  507. ==== Examples
  508. ===== Preset `datacenter_defaults`
  509. [source,yaml]
  510. ----
  511. # default trivago options
  512. include:
  513. - 'preset.pkgng_repos'
  514. - 'preset.nrpe_sudo'
  515. ----
  516. ===== Preset `pkgng_repos`
  517. [source,yaml]
  518. ----
  519. formula.pkgng:
  520. repos:
  521. FreeBSD:
  522. enabled: 'no'
  523. trivago:
  524. url: 'http://pkgmirror.trivago.trv/103x64/default'
  525. mirror_type: 'http'
  526. signature_type: 'pubkey'
  527. pubkey: '/etc/ssl/pkg.cert'
  528. enabled: 'yes'
  529. trivago-php:
  530. url: 'http://pkgmirror.trivago.trv/103x64/php5/'
  531. mirror_type: 'http'
  532. signature_type: 'pubkey'
  533. pubkey: '/etc/ssl/pkg.cert'
  534. trivago-php7:
  535. url: 'http://pkgmirror.trivago.trv/103x64/php7/'
  536. mirror_type: 'http'
  537. signature_type: 'pubkey'
  538. pubkey: '/etc/ssl/pkg.cert'
  539. ssl_terminator:
  540. url: 'http://pkgmirror.trivago.trv/103x64/libressl/'
  541. mirror_type: 'http'
  542. signature_type: 'pubkey'
  543. pubkey: '/etc/ssl/pkg.cert'
  544. ----
  545. ===== Role
  546. Examples:: `role.loadbalancer_datacenter`, `role.saltmaster_dev`,
  547. `role.ssl_terminator`
  548. Contains the role pillars that are matched to the <<role,role>> of the minion.
  549. They mirror the structure from the <<global,global pillars>>:
  550. ----
  551. role
  552. └── loadbalancer_datacenter
  553. ├── environment
  554. │   ├── dev
  555. │   └── prod
  556. ├── id
  557. │   ├── lb0-dus
  558. │   └── lb1-dus
  559. └── realm
  560. ├── asia
  561. │   └── hkg
  562. ├── eu
  563. │   ├── ams
  564. │   └── dus
  565. └── north_america
  566. ├── dca
  567. └── sfo
  568. ----
  569. This has the purpose to contain pillars to their role. This makes it easier to
  570. find the pillar files for the specific role. It also avoids unnecessary clutter
  571. in the pillars as only role that needs the pillars gets the pillars.
  572. When pillars need to be used by multiple roles there are two ways:
  573. * Define a <<global,global>> pillar applicable to the scope that is needed. This
  574. should be avoided if possible.
  575. * Define a <<preset,preset>> pillar that is then included into the role pillar.
  576. This is done like this:
  577. +
  578. .Role `ssl_terminator` pillar `init.sls`
  579. [source,yaml]
  580. ----
  581. include:
  582. - 'preset.datacenter_defaults'
  583. - 'role.ssl_terminator.base'
  584. ----
  585. ==== Matching
  586. The following grains are used for matching:
  587. `role`:: The <<role>> of the server.
  588. `environment`:: The <<environment>> of the server.
  589. `realm`:: The <<realm>> of the server.
  590. They will be matched in this order:
  591. . `role.<role>`
  592. . `global.realm.<realm>`
  593. . `role.<role>.realm.<realm>`
  594. . `global.environment.<environment>`
  595. . `role.<role>.environment.<environment>`
  596. . `global.id.<id>`
  597. . `role.<role>.id.<id>`
  598. The matching will also happen in the same order for `_secret` as a prefix where
  599. our pillars reside that are not included in the normal repository.
  600. // TODO: Add example with specific grains that shows where stuff will be put and
  601. // why they where put there.
  602. //== Examples
  603. //=== Role `saltmaster_dev` in realm `dus` with environment `prod`
  604. == Links
  605. * link:https://www.youtube.com/watch?v=yWhvgLqgYR0[Best Practices for Enterprise-Scale SaltStack - Trivago - SaltConf17]
  606. == Authors
  607. * Alexander Thaller <alexander.thaller@trivago.com>