Collections: why the name carries its own origin
Summary in one line
A collection bundles roles, modules, and plugins into one piece and is a unit distributed with a version attached, and a long name such as ansible.builtin.copy records the origin of that piece inside the name.
Why this is needed
Until 2019, Ansible shipped thousands of modules bundled in its core. To fix a single mysql_user, all of Ansible had to put out a new version, and conversely, upgrading Ansible changed thousands of modules you never used. The maintainers of community modules and the maintainers of Ansible core collided in the same repository, and releases grew heavier and heavier.
Name collisions were a real source of incidents too. Suppose you had an in-house deploy role and someone placed another role with the same name in roles/: from the log alone you could not tell which one had run. The order of the search path decided the outcome, and that order was decided not by people but by the environment.
Collections, introduced in Ansible 2.9, tackle both problems at once. They bundle the contents under <네임스페이스>.<이름> (namespace and name), attach a SemVer version to that bundle, and when you call something from a task you use a fully qualified collection name (FQCN), <네임스페이스>.<이름>.<모듈> (namespace, name, and module). From the name you can tell the origin, and even if two things have the same short name, their FQCNs do not overlap.
How it works
A collection source has a fixed directory layout. ansible-galaxy collection init <네임스페이스>.<이름> (with the namespace and name in place of the placeholders) creates that skeleton for you.
| Location | What it holds |
|---|---|
galaxy.yml |
Namespace, name, version, authors, license, tags. The build reads this file |
roles/ |
Roles. The conventions inside a role are exactly the same as before |
plugins/ |
Plugins such as modules, filters, lookups, and callbacks. Each type has a designated subdirectory |
playbooks/ |
Playbooks that the collection distributes along with it |
meta/runtime.yml |
Minimum ansible version (requires_ansible), renaming (plugin_routing), action groups |
docs/ |
Documentation |
The version in galaxy.yml must be SemVer. If you write two parts like 1.2, the build rejects it. Only with this rule does a range notation such as >=1.2.0,<2.0.0 have meaning.
Distribution takes three steps.
ansible-galaxy collection init acme.platform --init-path src # 뼈대
ansible-galaxy collection build --output-path dist # 묶음 만들기
ansible-galaxy collection install dist/acme-platform-1.2.0.tar.gz -p collections
What build produces is not just a tar.gz. Inside it are MANIFEST.json (metadata) and FILES.json (a checksum for each file). Installation uses this list to check integrity, so even if you just upload the bundle to an in-house file server, a machine can decide "is this the very version we built?"
You write the version to install in a file, not on the command line.
# requirements.yml
collections:
- name: acme.platform
version: "1.2.0" # Galaxy 나 사내 저장소에서 받을 때
- name: /srv/artifacts/acme-platform-1.2.0.tar.gz
type: file # 로컬 묶음 파일을 그대로 설치할 때
You install with ansible-galaxy collection install -r requirements.yml -p <경로> (where the placeholder is the install path). With a pinning file, even if the source repository has moved ahead to 1.3.0, what gets installed is 1.2.0. This one line is the biggest reason to adopt collections — the latest state of the repository and the version that gets deployed are separated.
The search order is also worth knowing. ansible looks at the paths listed in ANSIBLE_COLLECTIONS_PATH (or collections_path in ansible.cfg) and at the collections/ directory next to the playbook. If you write the collections: keyword in a play, you can call those collections by short names, but the official documentation recommends FQCN. Short names rely on the search order, and that order is decided by the environment.
What it looks like in the field
First, "I installed it but it can't be found." It is almost always a path problem. If the path you installed to with -p is not in the search path at run time, ansible does not know that collection. The habit of checking separately "is it there?" with ansible-galaxy collection list -p <경로> and "is it visible from here?" with ansible-doc -t role <FQCN> saves time.
Second, role argument specs. If you write the types, defaults, and allowed values of arguments in meta/argument_specs.yml, the role validates the arguments before it runs its first task and stops right there if they are wrong. If you distribute a role that others will use, this is both documentation and a line of defense. Without a spec, a wrong value blows up with an unrelated error around the fifth task.
Third, in-house distribution. Most code cannot be uploaded to Galaxy. In practice, CI uploads the bundle it builds to an internal file server or Artifactory, and each team's requirements.yml points to that address and version. The pipeline runs install -r requirements.yml once right before execution. With this structure, cases of "it worked yesterday but not today" decrease — because the version is written in a file.
Fourth, dependencies are not free. If you list other collections in dependencies: of galaxy.yml, they are pulled in at install time, but that applies only when there is a repository to pull from. In a network with no internet access, you have to carry the bundles of the dependent collections by hand as well. This lab environment is such a network, so automatic dependency resolution is not covered.
What you will do in the next lab
You build the acme.platform collection from the skeleton up, fill in galaxy.yml, put one role in it, enforce arguments with meta/argument_specs.yml, build the bundle and check its contents and MANIFEST, install it to a local path and look it up with ansible-doc, and call and run it by FQCN. At the end, you raise the source version to 1.3.0 and see with your own eyes that requirements.yml keeps holding 1.2.0.