diff --git a/docs/_data/breadcrumbs.yml b/docs/_data/breadcrumbs.yml index 1a8be27d83..6f66d9abea 100644 --- a/docs/_data/breadcrumbs.yml +++ b/docs/_data/breadcrumbs.yml @@ -15,6 +15,10 @@ en: /usage/build_draft: &documentation-usage-build_draft title: Build url: usage/build_draft/overview.html + # TODO: delete this temporary element + /usage/build_draft/stapel: &documentation-usage-build_draft-stapel + title: Build + url: usage/build_draft/overview.html /usage/build/run_in_containers: &documentation-usage-build-run_in_containers title: Run in containers url: usage/build/run_in_containers/use_docker_container.html @@ -77,6 +81,10 @@ ru: /usage/build_draft: <<: *documentation-usage-build_draft title: Сборка + # TODO: delete this temporary element + /usage/build_draft/stapel: + <<: *documentation-usage-build_draft-stapel + title: Stapel /usage/build: <<: *documentation-usage-build title: Сборка diff --git a/docs/_data/sidebars/_documentation.yml b/docs/_data/sidebars/_documentation.yml index beb5113ebc..c3cdccbc0e 100644 --- a/docs/_data/sidebars/_documentation.yml +++ b/docs/_data/sidebars/_documentation.yml @@ -49,34 +49,28 @@ entries: - title: Use Kubernetes url: /usage/build/run_in_containers/use_kubernetes.html - - title: Building images with stapel + - title: Building images with Stapel (UPDATED) f: - - title: Base image (required) - url: /usage/build/building_images_with_stapel/base_image.html + - title: Overview + url: /usage/build_draft/stapel/overview.html - - title: Adding docker instructions - url: /usage/build/building_images_with_stapel/docker_directive.html + - title: Base image + url: /usage/build_draft/stapel/base.html - title: Adding source code from git repositories - url: /usage/build/building_images_with_stapel/git_directive.html + url: /usage/build_draft/stapel/git.html - - title: Running assembly instructions - url: /usage/build/building_images_with_stapel/assembly_instructions.html - - - title: Reducing image size and speeding up a build by mounts - url: /usage/build/building_images_with_stapel/mount_directive.html + - title: User instructions + url: /usage/build_draft/stapel/instructions.html - title: Importing from images and artifacts - url: /usage/build/building_images_with_stapel/import_directive.html - - - title: Artifacts - url: /usage/build/building_images_with_stapel/artifacts.html + url: /usage/build_draft/stapel/imports.html - - title: Integration with SSH agent - url: /usage/build/building_images_with_stapel/integration_with_ssh_agent.html + - title: Dockerfile instructions + url: /usage/build_draft/stapel/dockerfile.html - - title: Stage Introspection - url: /usage/build/building_images_with_stapel/stage_introspection.html + - title: Reducing image size and speeding up a build by mounts + url: /usage/build/building_images_with_stapel/mounts.html - title: Deploy f: @@ -246,34 +240,28 @@ entries: - title: В Kubernetes url: /usage/build/run_in_containers/use_kubernetes.html - - title: Сборка образов с помощью Stapel + - title: Сборка образов с помощью Stapel (ОБНОВЛЕНИЕ) f: - - title: Базовый образ - url: /usage/build/building_images_with_stapel/base_image.html + - title: Обзор + url: /usage/build_draft/stapel/overview.html - - title: Использование Docker-инструкций - url: /usage/build/building_images_with_stapel/docker_directive.html + - title: Базовый образ + url: /usage/build_draft/stapel/base.html - title: Добавление исходного кода из git-репозиториев - url: /usage/build/building_images_with_stapel/git_directive.html - - - title: Запуск инструкций сборки - url: /usage/build/building_images_with_stapel/assembly_instructions.html + url: /usage/build_draft/stapel/git.html - - title: Ускорение сборки и уменьшение размера за счёт монтирования - url: /usage/build/building_images_with_stapel/mount_directive.html + - title: Пользовательские инструкции + url: /usage/build_draft/stapel/instructions.html - title: Импорт из артефактов и образов - url: /usage/build/building_images_with_stapel/import_directive.html + url: /usage/build_draft/stapel/imports.html - - title: Артефакты - url: /usage/build/building_images_with_stapel/artifacts.html + - title: Инструкции Dockerfile + url: /usage/build_draft/stapel/dockerfile.html - - title: Интеграция с SSH-агентом - url: /usage/build/building_images_with_stapel/integration_with_ssh_agent.html - - - title: Интроспекция стадий - url: /usage/build/building_images_with_stapel/stage_introspection.html + - title: Ускорение сборки и уменьшение размера за счёт монтирования + url: /usage/build/building_images_with_stapel/mounts.html - title: Развертывание f: diff --git a/docs/pages_en/usage/build_draft/stapel/base.md b/docs/pages_en/usage/build_draft/stapel/base.md new file mode 100644 index 0000000000..25134a1b61 --- /dev/null +++ b/docs/pages_en/usage/build_draft/stapel/base.md @@ -0,0 +1,64 @@ +--- +title: Base image +permalink: usage/build_draft/stapel/base.html +author: Alexey Igrychev +directive_summary: base_image +--- + +Here's a minimal `werf.yaml`. It describes a _image_ named `example` that is based on a _base image_ named `alpine`: + +```yaml +project: my-project +configVersion: 1 +--- +image: example +from: alpine +``` + +_Base image_ can be declared with `from`, `fromImage` or `fromArtifact` directive. + +## from, fromLatest + +The `from` directive defines the name and tag of a _base image_. If absent, tag defaults to `latest`. + +```yaml +from: [:] +``` + +By default, the assembly process does not depend on actual _base image_ digest in the repository, only on _from_ directive value. +Thus, changing _base image_ locally or in the repository does not matter if _from_ stage is already exists in _storage_. + +If you want always build the image with actual _base image_ you should use _fromLatest_ directive. +_fromLatest_ directive allows connecting the assembly process with the _base image_ digest getting from the repository. + +```yaml +fromLatest: true +``` + +> By default, the use of the `fromLatest` directive is not allowed by giterminism (read more about it [here]({{ "usage/project_configuration/giterminism.html" | true_relative_url }})) + +## fromImage and fromArtifact + +Besides using docker image from a repository, the _base image_ can refer to _image_ or [_artifact_]({{ "usage/build_draft/stapel/imports.html#what-is-an-artifact" | true_relative_url }}), that is described in the same `werf.yaml`. + +```yaml +fromImage: +fromArtifact: +``` + +If a _base image_ is specific to a particular application, +it is reasonable to store its description with _images_ and _artifacts_ which are used it as opposed to storing the _base image_ in a container registry. + +Also, this method can be useful if the stages of _stage conveyor_ are not enough for building the image. You can design your _stage conveyor_. + + + Conveyor with fromImage and fromArtifact stages + + +## fromCacheVersion + +The `fromCacheVersion` directive allows managing image reassembly. + +```yaml +fromCacheVersion: +``` diff --git a/docs/pages_en/usage/build_draft/stapel/dockerfile.md b/docs/pages_en/usage/build_draft/stapel/dockerfile.md new file mode 100644 index 0000000000..0947c3e2c9 --- /dev/null +++ b/docs/pages_en/usage/build_draft/stapel/dockerfile.md @@ -0,0 +1,39 @@ +--- +title: Adding docker instructions +permalink: usage/build_draft/stapel/dockerfile.html +author: Alexey Igrychev +directive_summary: docker +--- + +[Dockerfile instructions](https://docs.docker.com/engine/reference/builder/) can be divided into two groups: build-time instructions and other instructions that effect on an image manifest. Build-time instructions do not make sense in a werf build process. Therefore, werf supports only following instructions: + +* `USER` to set the user name (or UID) and optionally the user group (or GID) (read more [here](https://docs.docker.com/engine/reference/builder/#user)). +* `WORKDIR` to set the working directory (read more [here](https://docs.docker.com/engine/reference/builder/#workdir)). +* `VOLUME` to add mount point (read more [here](https://docs.docker.com/engine/reference/builder/#volume)). +* `ENV` to set the environment variable (read more [here](https://docs.docker.com/engine/reference/builder/#env)). +* `LABEL` to add metadata to an image (read more [here](https://docs.docker.com/engine/reference/builder/#label)). +* `EXPOSE` to inform Docker that the container listens on the specified network ports at runtime (read more [here](https://docs.docker.com/engine/reference/builder/#expose)) +* `ENTRYPOINT` to configure a container that will run as an executable (read more [here](https://docs.docker.com/engine/reference/builder/#entrypoint)). How stapel builder processes CMD and ENTRYPOINT is covered [here]({{ "/usage/build/build_process.html#how-the-stapel-builder-processes-cmd-and-entrypoint" | true_relative_url }}). +* `CMD` to provide default arguments for the `ENTRYPOINT` to configure a container that will run as an executable (read more [here](https://docs.docker.com/engine/reference/builder/#cmd)). How stapel builder processes CMD and ENTRYPOINT is covered [here]({{ "/usage/build/build_process.html#how-the-stapel-builder-processes-cmd-and-entrypoint" | true_relative_url }}). +* `HEALTHCHECK` to tell Docker how to test a container to check that it is still working (read more [here](https://docs.docker.com/engine/reference/builder/#healthcheck)) + +These instructions can be specified in the `docker` config directive. + +Here is an example of using docker instructions: + +```yaml +docker: + WORKDIR: /app + CMD: ["python", "./index.py"] + EXPOSE: '5000' + ENV: + TERM: xterm + LC_ALL: en_US.UTF-8 +``` + +Defined docker instructions are applied on the last stage called `docker_instructions`. +Thus, instructions do not affect other stages, ones just will be applied to a built image. + +If need to use special environment variables in build-time of your application image, such as `TERM` environment, you should use a [base image]({{ "usage/build_draft/stapel/base.html" | true_relative_url }}) with these variables. + +> Tip: you can also implement exporting environment variables right in [_user stage_]({{ "usage/build/building_images_with_stapel/assembly_instructions.html#what-are-user-stages" | true_relative_url }}) instructions diff --git a/docs/pages_en/usage/build_draft/stapel/git.md b/docs/pages_en/usage/build_draft/stapel/git.md new file mode 100644 index 0000000000..548feea2db --- /dev/null +++ b/docs/pages_en/usage/build_draft/stapel/git.md @@ -0,0 +1,301 @@ +--- +title: Adding source code from git repositories +permalink: usage/build_draft/stapel/git.html +directive_summary: git +--- + +## What is git mapping? + +***Git mapping*** describes a file or a directory in the git repository that should be added to the image at the particular path. The repository may be a local one, hosted in the directory that contains the config, or a remote one. In the latter case, the configuration of the _git mapping_ includes a repository address and version (branch, tag, or commit hash). + +werf adds files from the repository to the image either by fully transferring them via git archive or by applying patches between commits. The full transfer is used to add files initially. Subsequent builds apply patches to reflect changes in a git repository. You can learn more about the algorithm behind fully transferring and applying patches in the [More details: git_archive...](#more-details-gitarchive-gitcache-gitlatestpatch) section. + +The configuration of _git mappings_ supports filtering of files, and you can use a set of _git mappings_ to create virtually any file structure in the image. Also, you can specify the owner and the group of files in the _git mapping_ configuration, without the need to run `chown`. + +werf has support for submodules. If it detects that files specified in the _git mapping_ configuration are present in submodules, it would act accordingly in order to change files in submodules correctly. + +> All submodules of the project are bound to a specific commit. Thus, all collaborators get the same content. werf **does not initialize or update submodules**. Instead, it merely uses these bound commits + +Here is an example of a _git mapping_ configuration. It adds source files from a local repository (here, `/src` is the source, and `/app` is the destination directory), and imports remote phantomjs source files to `/src/phantomjs`: + +```yaml +git: +- add: /src + to: /app +- url: https://github.com/ariya/phantomjs + add: / + to: /src/phantomjs +``` + +## Syntax of a git mapping + +The _git mapping_ configuration for a local repository has the following parameters: + +- `add` — path to a directory or a file whose contents must be copied into the image. The path must be specified relative to the repository root, and it is absolute (i.e., starts with `/`). This parameter is optional, contents of the entire repository are transferred by default, i.e., an empty `add` is equivalent to `add: /`; +- `to` — the path in the image to copy the contents specified with `add`; +- `owner` — the name or uid of the owner of the files to be copied; +- `group` — the name or gid of the owner’s group; +- `excludePaths` — a set of masks to exclude files or directories during recursive copying. Paths in masks must be specified relative to add; +- `includePaths` — a set of masks to include files or directories during recursive copying. Paths in masks must be specified relative to add; +- `stageDependencies` — a set of masks to monitor for changes that lead to rebuilds of the user stages. This is reviewed in detail in the [Running assembly instructions]({{ "usage/build/building_images_with_stapel/assembly_instructions.html" | true_relative_url }}) reference. + +The configuration of a _git mapping_ for a remote repository has some additional parameters: +- `url` — address of the remote repository; +- `branch`, `tag`, `commit` — the name of a branch, tag, or a commit hash that will be used. If these parameters are omitted, the master branch is used instead. + +> By default, the use of the `branch` directive is not allowed by giterminism (read more about it [here]({{ "usage/project_configuration/giterminism.html" | true_relative_url }})) + +## Using git mappings + +### Copying directories + +The `add` parameter defines a source path in a repository. Then all files in this directory are recursively retrieved and added to the image at the `to` path. If the parameter is not set, werf uses the default path ( `/` ) instead. In other words, the entire repository will be copied. For example: + +```yaml +git: +- add: / + to: /app +``` + +This basic _git mapping_ configuration adds entire contents of the repository to the `/app` directory in the image. + + +
+ git repository files tree +
+
+ image files tree +
+ +You can specify multiple _git mappings_: + +```yaml +git: +- add: /src + to: /app/src +- add: /assets + to: /static +``` + + +
+ git repository files tree +
+
+ image files tree +
+ +It should be noted, however, that the _git mapping_ doesn't specify a directory to be transferred (similarly to `cp -r /src /app`). Instead, the `add` parameter specifies the contents of a directory that will be transferred from the repository recursively. That is, if you need to copy the contents of the `/assets` directory to the `/app/assets` directory, then you have to specify the **assets** keyword twice in the configuration or use the `includePaths` [filter](#using-filters). For example: + +```yaml +git: +- add: /assets + to: /app/assets +``` + +or + +```yaml +git: +- add: / + to: /app + includePaths: assets +``` + +### Changing the owner + +The _git mapping_ configuration provides the `owner` and `group` parameters. These are the names or numerical ids of the owner and group common to all files and directories transferred to the image. + +```yaml +git: +- add: /src/index.php + to: /app/index.php + owner: www-data +``` + +![index.php owned by www-data user and group]({{ "images/build/git_mapping_05.png" | true_relative_url }}) + +If the `group` parameter is omitted, then the group is set to the primary group of the user. + +If the `owner` or `group` value is a string, then the specified user or group must exist in the system by the moment the transfer of files is complete. Otherwise, the build would end with an error. + +```yaml +git: +- add: /src/index.php + to: /app/index.php + owner: wwwdata +``` + + + +### Using filters + +`includePaths` and `excludePaths` parameters help werf to process the file list. These are the sets of masks that you can use to include and exclude files and directories to/from the list of files to transfer to the image. The `excludePaths` filter works as follows: masks are applied to each file found in the `add` path. If there is at least one match, then the file is ignored; if no matches are found, then the file gets added to the image. `includePaths` works the opposite way: if there is at least one match, then the file gets added to the image. + +_Git mapping_ configuration can contain both filters. In this case, a file is added to the image if the path matches any of `includePaths` masks and not match all `excludePaths` masks. + +For example: + +```yaml +git: +- add: /src + to: /app + includePaths: + - '**/*.php' + - '**/*.js' + excludePaths: + - '**/*-dev.*' + - '**/*-test.*' +``` + +This git mapping configuration adds `.php` and `.js` files from `/src` except for files with suffixes starting with `-dev.` or `-test.`. + +> The step involving the addition of a `**/*` template is here for convenience: the most common use case of a _git mapping_ with filters is to configure recursive copying for the directory. The addition of `**/*` allows you to specify the directory name only; thus, its entire contents would match the filter + +Masks have the following wildcards: + +- `*` — matches any file. This pattern includes `.` and excludes `/` +- `**` — matches directories recursively or files expansively +- `?` — matches exactly one character. It is equivalent to /.{1}/ in regexp +- `[set]` — matches any character within the set. It behaves exactly like character sets in regexp, including the set negation ([^a-z]) +- `\` — escapes the next metacharacter + +Masks that start with `*` or `**` should be escaped with quotation marks in the `werf.yaml` file: + - `"*.rb"` — with double quotation marks +- `'**/*'` — with single quotation marks + +Examples of filters: + +```yaml +add: /src +to: /app +includePaths: +# match all php files residing directly in /src +- '*.php' + +# match recursively all php files in /src +# (also matches *.php because '.' is included in **) +- '**/*.php' + +# match all files in /src/module1 recursively +# an example of the implicit addition of **/* +- module1 +``` + +You can use the `includePaths` filter to copy a single file without renaming it: +```yaml +git: +- add: /src + to: /app + includePaths: index.php +``` + +### Overlapping of target paths + +Those who prefer to add multiple _git mappings_ need to remember that overlapping paths defined in `to` may result in the inability to add files to the image. For example: + +```yaml +git: +- add: /src + to: /app +- add: /assets + to: /app/assets +``` + +When processing a config, werf calculates possible overlaps among all _git mappings_ related to `includePaths` and `excludePaths` filters. If an overlap is detected, werf tries to resolve the conflict by adding `excludePaths` into the _git mapping_ implicitly. In all other cases, the build ends with an error. However, the implicit `excludePaths` filter can have undesirable side effects, so it is better to avoid conflicts caused by overlapping paths between configured git mappings. + +Here is an implicit `excludePaths` example: + +```yaml +git: +- add: /src + to: /app + excludePaths: # werf add this filter to resolve a conflict + - assets # between paths /src/assets and /assets +- add: /assets + to: /app/assets +``` + +## Working with remote repositories + +werf can use remote repositories as file sources. For this, you have to specify the repository address via the `url` parameter in the _git mapping_ configuration. werf supports `https` and `git+ssh` protocols. + +### https + +Here is the syntax for the https protocol: + +{% raw %} +```yaml +git: +- url: https://[USERNAME[:PASSWORD]@]repo_host/repo_path[.git/] +``` +{% endraw %} + +To access the repository over `https`, you may need to enter login and password. + +Here is an example of using GitLab CI variables for getting a login and password: + +{% raw %} +```yaml +git: +- url: https://{{ env "CI_REGISTRY_USER" }}:{{ env "CI_JOB_TOKEN" }}@registry.gitlab.company.name/common/helper-utils.git +``` +{% endraw %} + +In the above example, we use the [env](http://masterminds.github.io/sprig/os.html) method from the sprig library for accessing the environment variables. + +### git, ssh + +werf supports accessing the repository via the git protocol. Commonly, this protocol is secured with ssh: this feature is used by GitHub, Bitbucket, GitLab, Gogs, Gitolite, etc. Generally, the repository address will look as follows: + +```yaml +git: +- url: git@gitlab.company.name:project_group/project.git +``` + +A good understanding of the process of werf searching for access keys is required to use the remote repositories over ssh (read more below). + +#### Working with ssh keys + +The ssh-agent provides keys for ssh connections. It is a daemon operating via a file socket. The path to the socket is stored in the environment variable `SSH_AUTH_SOCK`. werf mounts this file socket into all _assembly containers_ and sets the environment variable `SSH_AUTH_SOCK`, i.e., connection to remote git repositories is established using keys registered in the running ssh-agent. + +werf applies the following algorithm for using the ssh-agent: + +- If werf is started with the `--ssh-key` flag (there might be multiple flags): + - A temporary ssh-agent starts and uses the defined keys; it is used for all git operations with remote repositories. + - The already running ssh-agent is ignored in this case. +- No `--ssh-key` flag(s) is specified and ssh-agent is running: + - werf uses the `SSH_AUTH_SOCK` environment variable; keys that are added to this agent are used for git operations. +- No `--ssh-key` flag(s) is specified and ssh-agent is not running: + - If the `~/.ssh/id_rsa` file exists, werf runs the temporary ssh-agent with the key contained in the `~/.ssh/id_rsa` file. +- If none of the previous options is applicable, then the ssh-agent does not start. Thus, no keys for git operations are available and building images using remote _git mappings_ ends with an error. + +## More details: gitArchive, gitCache, gitLatestPatch + +Let us review the process of adding files to the resulting image in more detail. As it was stated earlier, the docker image contains multiple layers. To understand what layers werf create, let's consider the building actions based on three sample commits: `1`, `2` and `3`: + +- Build of a commit No. 1. All files are added to a single layer depending on the configuration of the _git mappings_. This is done with the help of the git archive command. The resulting layer corresponds to the _gitArchive_ stage. +- Build of a commit No. 2. Another layer is added. In it, files are modified by applying a patch. This layer corresponds to the _gitLatestPatch_ stage. +- Build of a commit No. 3. Files have been added already, and werf applies patches in the _gitLatestPatch_ stage layer. + +The build sequence for these commits may be represented as follows: + +| | gitArchive | gitLatestPatch | +|---|:---:|:---:| +| Commit No. 1 is made, build at 10:00 | files as in commit No. 1 | - | +| Commit No. 2 is made, build at 10:05 | files as in commit No. 1 | files as in commit No. 2 | +| Commit No. 3 is made, build at 10:15 | files as in commit No. 1 | files as in commit No. 3 | + +With time, the number of commits grows, and the size of the patch between commit No. 1 and the current one may become quite large. It will further increase the size of the latest layer and the total size of stages. To prevent the uncontrolled growth of the latest layer, werf provides the additional intermediary stage — _gitCache_. When _gitLatestPatch_ diff becomes too big, much of its diff is merged with the _gitCache_ diff, thus reducing the _gitLatestPatch_ stage size. + +### _git stages_ and rebasing + +Each _git stage_ stores service labels containing SHA commits that this _stage_ was built up on. +werf will use them for creating patches when assembling the next _git stage_ (in a nutshell, it is a `git diff COMMIT_FROM_PREVIOUS_GIT_STAGE LATEST_COMMIT` for each described _git mapping_). +So, if some stage has a saved commit that is not in a git repository (e.g., after rebasing), then werf would rebuild that stage at the next build using the latest commits. diff --git a/docs/pages_en/usage/build_draft/stapel/imports.md b/docs/pages_en/usage/build_draft/stapel/imports.md new file mode 100644 index 0000000000..c4a99cba19 --- /dev/null +++ b/docs/pages_en/usage/build_draft/stapel/imports.md @@ -0,0 +1,177 @@ +--- +title: Importing from images and artifacts +permalink: usage/build_draft/stapel/imports.html +author: Alexey Igrychev +directive_summary: import +--- + +The size of the image can be increased several times due to the assembly tools and source files, while the user does not need them. +To solve such problems, Docker community suggests doing the installation of tools, the assembly, and removal in one step. + +``` +RUN “download-source && cmd && cmd2 && remove-source” +``` + +> To obtain a similar effect when using werf, it is sufficient describing the instructions in one _user stage_. For example, _shell assembly instructions_ for the _install stage_ (similarly for _ansible_): +```yaml +shell: + install: + - "download-source" + - "cmd" + - "cmd2" + - "remove-source" +``` + +However, with this method, it is not possible using a cache, so toolkit installation runs each time. + +Another solution is using multi-stage builds, which are supported starting with Docker 17.05. + +``` +FROM node:latest AS storefront +WORKDIR /app +COPY react-app . +RUN npm install +RUN npm run build + +FROM maven:latest AS appserver +WORKDIR /app +COPY . . +RUN mvn package + +FROM java:8-jdk-alpine +COPY --from=storefront /app/react-app/build/ /static +COPY --from=appserver /app/target/AtSea-0.0.1-SNAPSHOT.jar /app/AtSea.jar +``` + +The meaning of such an approach is as follows, describe several auxiliary images and selectively copy artifacts from one image to another leaving behind everything you do not want in the result image. + +We suggest the same, but using [_images_]({{ "reference/werf_yaml.html#image-section" | true_relative_url }}) and [_artifacts_]({{ "reference/werf_yaml.html#image-section" | true_relative_url }}). + +> Why is werf not using multi-stage? +* Historically, _imports_ appeared much earlier than Docker multi-stage, and +* werf gives more flexibility working with auxiliary images + +Importing _resources_ from _images_ and _artifacts_ should be described in `import` directive in _destination image_ config section ([_image_]({{ "reference/werf_yaml.html#image-section" | true_relative_url }}) or [_artifact_]({{ "reference/werf_yaml.html#image-section" | true_relative_url }}). `import` is an array of records. Each record should contain the following: + +- `image: ` or `artifact: `: _source image_, image name from which you want to copy files. +- `stage: `: _source image stage_, particular stage of _source_image_ from which you want to copy files. +- `add: `: _source path_, absolute file or folder path in _source image_ for copying. +- `to: `: _destination path_, absolute path in _destination image_. In case of absence, _destination path_ equals _source path_ (from `add` directive). +- `before: ` or `after: `: _destination image stage_, stage for importing files. At present, only _install_ and _setup_ stages are supported. + +```yaml +import: +- artifact: application-assets + add: /app/public/assets + to: /var/www/site/assets + after: install +- image: frontend + add: /app/assets + after: setup +``` + +As in the case of adding _git mappings_, masks are supported for including, `include_paths: []`, and excluding files, `exclude_paths: []`, from the specified path. +You can also define the rights for the imported resources, `owner: ` and `group: `. +Read more about these in the [git directive article]({{ "usage/build_draft/stapel/git.html" | true_relative_url }}). + +> Import paths and _git mappings_ must not overlap with each other + +Information about _using artifacts_ available in [separate article]({{ "usage/build_draft/stapel/imports.html#what-is-an-artifact" | true_relative_url }}). + +## What is an artifact? + +***Artifact*** is a special image that is used by _images_ and _artifacts_ to isolate the build process and build tools resources (environments, software, data). + +Using artifacts, you can independently assemble an unlimited number of components, and also solving the following problems: + +- The application can consist of a set of components, and each has its dependencies. With a standard assembly, you should rebuild all every time, but you want to assemble each one on-demand. +- Components need to be assembled in other environments. + +Importing _resources_ from _artifacts_ are described in [import directive]({{ "usage/build/building_images_with_stapel/import_directive.html" | true_relative_url }}). + +### Configuration + +The configuration of the _artifact_ is not much different from the configuration of _image_. Each _artifact_ should be described in a separate artifact config section. + +The instructions associated with the _from stage_, namely the [_base image_]({{ "usage/build_draft/stapel/base.html" | true_relative_url }}) and [mounts]({{ "usage/build/building_images_with_stapel/mount_directive.html" | true_relative_url }}), and also [imports]({{ "usage/build/building_images_with_stapel/import_directive.html" | true_relative_url }}) remain unchanged. + +The _docker_instructions stage_ and the [corresponding instructions]({{ "usage/build/building_images_with_stapel/docker_directive.html" | true_relative_url }}) are not supported for the _artifact_. An _artifact_ is an assembly tool and only the data stored in it is required. + +The remaining _stages_ and instructions are considered further separately. + +#### Naming + +
+```yaml +artifact: string +``` +
+ +_Artifact images_ are declared with `artifact` directive: `artifact: string`. Unlike the [name of the _image_]({{ "reference/werf_yaml.html#image-section" | true_relative_url }}), the artifact has no limitations associated with docker naming convention, as used only internal. + +```yaml +artifact: "application assets" +``` + +#### Adding source code from git repositories + +
+ + + + + +
+ +Unlike with _image_, _artifact stage conveyor_ has no _gitCache_ and _gitLatestPatch_ stages. + +> werf implements optional dependence on changes in git repositories for _artifacts_. Thus, by default werf ignores them and _artifact image_ is cached after the first assembly, but you can specify any dependencies for assembly instructions + +Read about working with _git repositories_ in the corresponding [article]({{ "usage/build_draft/stapel/git.html" | true_relative_url }}). + +#### Running assembly instructions + +
+ + + + + +
+ +Directives and _user stages_ remain unchanged: _beforeInstall_, _install_, _beforeSetup_ and _setup_. + +If there are no dependencies on files specified in git `stageDependencies` directive for _user stages_, the image is cached after the first build and will no longer be reassembled while the related _stages_ exist in _storage_. + +> If the artifact should be rebuilt on any change in the related git repository, you should specify the _stageDependency_ `**/*` for any _user stage_, e.g., for _install stage_: +```yaml +git: +- to: / + stageDependencies: + install: "**/*" +``` + +Read about working with _assembly instructions_ in the corresponding [article]({{ "usage/build/building_images_with_stapel/assembly_instructions.html" | true_relative_url }}). + +## Using artifacts + +Unlike [*stapel image*]({{ "usage/build/building_images_with_stapel/assembly_instructions.html" | true_relative_url }}), *stapel artifact* does not have a git latest patch stage. + +The git latest patch stage is supposed to be updated on every commit, which brings new changes to files. *Stapel artifact* though is recommended to be used as a deeply cached image, which will be updated in rare cases, when some special files changed. + +For example: import git into *stapel artifact* and rebuild assets in this artifact only when dependent assets files in git has changes. For every other change in git where non-dependent files has been changed assets will not be rebuilt. + +However in the case when there is a need to bring changes of any git files into *stapel artifact* (to build Go application for example) user should define `git.stageDependencies` of some stage that needs these files explicitly as `*` pattern: + +``` +git: +- add: / + to: /app + stageDependencies: + setup: + - "*" +``` + +In this case every change in git files will result in artifact rebuild, all *stapel images* that import this artifact will also be rebuilt. + +**NOTE** User should employ multiple separate `git.add` directive invocations in every [*stapel image*]({{ "usage/build/building_images_with_stapel/assembly_instructions.html" | true_relative_url }}) and *stapel artifact* that needs git files — it is an optimal way to add git files into any image. Adding git files to artifact and then importing it into image using `import` directive is not recommended. diff --git a/docs/pages_en/usage/build_draft/stapel/instructions.md b/docs/pages_en/usage/build_draft/stapel/instructions.md new file mode 100644 index 0000000000..16527bd33e --- /dev/null +++ b/docs/pages_en/usage/build_draft/stapel/instructions.md @@ -0,0 +1,474 @@ +--- +title: Running assembly instructions +permalink: usage/build_draft/stapel/instructions.html +directive_summary: shell_and_ansible +--- + +## What are user stages? + +***User stage*** is a [_stage_]({{ "usage/build/stages_and_storage.html" | true_relative_url }}) containing _assembly instructions_ from the config. +Currently, there are two kinds of assembly instructions: _shell_ and _ansible_. werf provides four user stages and executes them in the following order: _beforeInstall_, _install_, _beforeSetup_, and _setup_. You can create the specific docker layer by executing assembly instructions contained within the respective stage. + + +## Using user stages + +werf provides four _user stages_ where assembly instructions can be defined. werf does not impose any restrictions on assembly instructions. You can specify the same variety of instructions as for the `RUN` instruction in Dockerfile. At the same time, the categorization of assembly instructions is based on our experience with real applications. So, the following actions are enough for building the vast majority of applications: + +- install system packages; +- install system dependencies; +- install application dependencies; +- setup system applications; +- setup application. + +What is the best strategy to execute them? You might think the best way is to execute them one by one while caching interim results. On the other side, it is better not to mix instructions for these actions because of different file dependencies. The _user stages pattern_ suggests the following strategy: + +- use the _beforeInstall_ user stage for installing system packages +- use the _install_ user stage to install system and application dependencies +- use the _beforeSetup_ user stage to configure system parameters and install an application +- use the _setup_ user stage to configure an application + +### beforeInstall + +This stage executes various instructions before installing an application. It is best suited for system applications that rarely change. At the same time, their installation process is very time-consuming. Also, at this stage, you can configure some system parameters that rarely change, such as setting a locale or a timezone, adding groups and users, etc. For example, you can install language distributions and build tools like PHP and Composer, Java and Gradle, and so on, at this stage. + +In practice, all these components rarely change, and the _beforeInstall_ stage caches +them for an extended period. + +### install + +This stage is for installing an application. It is best suited for installing application dependencies and configuring some basic settings. + +Instructions on this stage have access to application source codes, so you can install application dependencies using build tools (like Composer, Gradle, npm, etc.) that require a manifest file (e.g., pom.xml, Gruntfile) to work. A best practice is to make this stage dependent on changes in that manifest file. + +### beforeSetup + +At this stage, you can prepare an application for tuning some parameters. It supports all kinds of compiling tasks: creating jars, creating executable files and dynamic libraries, creating web assets, uglification, and encryption. This stage is often made dependent on changes in the source code. + +### setup + +This stage is intended for configuring application settings. The corresponding set of actions includes copying some profiles into `/etc`, copying configuration files to already-known locations, creating a file containing the application version. These actions should not be time-consuming since they will likely be executed on every commit. + +### custom strategy + +Once again, no limitations are imposed on assembly instructions. The previous definitions of _user stages_ are just suggestions arising from our experience with real-world applications. You can even use merely a single _user stage_ or define your strategy for grouping assembly instructions and benefit from caching and Git dependencies. + +## Syntax + +There are two top-level ***builder directives*** for assembly instructions that are mutually exclusive: `shell` and `ansible`. You can build an image either via ***shell instructions*** or via their ***ansible counterparts***. + +The _builder directive_ includes four directives that define assembly instructions for each _user stage_: +- `beforeInstall`; +- `install`; +- `beforeSetup`; +- `setup`. + +Builder directives can also contain ***cacheVersion directives*** that, in essence, are user-defined parts of _user-stage digests_. The detailed information is available in the [CacheVersion](#dependency-on-the-cacheversion-value) section. + +## Shell + +Here is the syntax for _user stages_ containing _shell assembly instructions_: + +```yaml +shell: + beforeInstall: + - + - + ... + - + install: + - bash command + ... + beforeSetup: + - bash command + ... + setup: + - bash command + ... + cacheVersion: + beforeInstallCacheVersion: + installCacheVersion: + beforeSetupCacheVersion: + setupCacheVersion: +``` + +_Shell assembly instructions_ are made up of arrays. Each array consists of bash commands for the related _user stage_. Commands for each stage are executed as a single `RUN` instruction in Dockerfile. Thus, werf creates one layer for each _user stage_. + +werf provides distribution-agnostic bash binary, so you do not need a bash binary in the [base image]({{ "usage/build_draft/stapel/base.html" | true_relative_url }}). + +```yaml +beforeInstall: +- apt-get update +- apt-get install -y build-essential g++ libcurl4 +``` + +The `bash` binary is stored in a _stapel volume_. You can find additional information about the concept in the [blog post [RU]](https://habr.com/company/flant/blog/352432/) (`dappdeps` was later renamed to `stapel`; nevertheless, the principle is the same) + +## Ansible + +Here is the syntax for _user stages_ containing _ansible assembly instructions_: + +```yaml +ansible: + beforeInstall: + - + - + ... + - + install: + - ansible task + ... + beforeSetup: + - ansible task + ... + setup: + - ansible task + ... + cacheVersion: + beforeInstallCacheVersion: + installCacheVersion: + beforeSetupCacheVersion: + setupCacheVersion: +``` + +### Ansible config and stage playbook + +_Ansible assembly instructions_ for _user stage_ is a set of ansible tasks. + +Generated `ansible.cfg` contains settings for ansible: +- use local transport (transport = local); +- werf's stdout_callback method for better logging (stdout_callback = werf); +- turn on the force_color mode (force_color = 1); +- use sudo for privilege escalation (to avoid using `become` in ansible tasks). + +Generated `playbook.yml` is a playbook with all tasks from the specific _user stage_. Here is an example of `werf.yaml` that includes the _install_ stage: + +```yaml +ansible: + install: + - debug: msg='Start install' + - file: path=/etc mode=0777 + - copy: + src: /bin/sh + dest: /bin/sh.orig + - apk: + name: curl + update_cache: yes + ... +``` + +`ansible` and `python` binaries/libraries are stored in a _stapel volume_. You can find more information about this concept in [this blog post [RU]](https://habr.com/company/flant/blog/352432/) (`dappdeps` was later renamed to `stapel`; nevertheless, the principle is the same). + +### Supported modules + +One of the ideas at the core of werf is idempotent builds. werf must generate the very same image every time if there are no changes. We solve this task by calculating a _digest_ for _stages_. However, ansible’s modules are non-idempotent, meaning they produce different results even if the input parameters are the same. Thus, werf is unable to correctly calculate a _digest_ in order to determine the need to rebuild _stages_. Because of that, werf currently supports a limited list of modules: + +- [Command modules](https://docs.ansible.com/ansible/2.5/modules/list_of_commands_modules.html): command, shell, raw, script. +- [Crypto modules](https://docs.ansible.com/ansible/2.5/modules/list_of_crypto_modules.html): openssl_certificate, and other. +- [Files modules](https://docs.ansible.com/ansible/2.5/modules/list_of_files_modules.html): acl, archive, copy, stat, tempfile, and other. +- [Net Tools Modules](https://docs.ansible.com/ansible/2.5/modules/list_of_net_tools_modules.html): get_url, slurp, uri. +- [Packaging/Language modules](https://docs.ansible.com/ansible/2.5/modules/list_of_packaging_modules.html#language): composer, gem, npm, pip, and other. +- [Packaging/OS modules](https://docs.ansible.com/ansible/2.5/modules/list_of_packaging_modules.html#os): apt, apk, yum, and other. +- [System modules](https://docs.ansible.com/ansible/2.5/modules/list_of_system_modules.html): user, group, getent, locale_gen, timezone, cron, and other. +- [Utilities modules](https://docs.ansible.com/ansible/2.5/modules/list_of_utilities_modules.html): assert, debug, set_fact, wait_for. + +An attempt to do a _werf config_ with the module not in this list will lead to an error, and a failed build. Feel free to report an [issue](https://github.com/werf/werf/issues/new) if some module should be enabled. + +### Copying files + +[Git mappings]({{ "usage/build_draft/stapel/git.html" | true_relative_url }}) are the preferred way of copying files into an image. werf cannot detect changes to the files referred to in the `copy` module. Currently, the only way to copy some external file into an image involves using the `.Files.Get` method of Go templates. This method returns the contents of the file as a string. Thus, the contents become a part of the _user stage digest_, and file changes lead to the rebuild of the _user stage_. + +Here is an example of copying `nginx.conf` into an image: + +{% raw %} +```yaml +ansible: + install: + - copy: + content: | +{{ .Files.Get "/conf/etc/nginx.conf" | indent 8 }} + dest: /etc/nginx/nginx.conf +``` +{% endraw %} + +werf renders that snippet as a go template and then transforms it into the following `playbook.yml`: + +```yaml +- hosts: all + gather_facts: no + tasks: + install: + - copy: + content: | + http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + ... +``` + +### Jinja templates + +Ansible supports [Jinja templates](https://docs.ansible.com/ansible/2.5/user_guide/playbooks_templating.html) in playbooks. However, Go templates and Jinja templates have the same delimiters: {% raw %}{{ and }}{% endraw %}. Thus, you have to escape Jinja templates to use them. There are two possible solutions: you can escape {% raw %}{{{% endraw %} delimiters only or escape the whole Jinja expression. + +Let’s take a look at the example. Say, you have the following ansible task: + +{% raw %} +```yaml +- copy: + src: {{item}} + dest: /etc/nginx + with_files: + - /app/conf/etc/nginx.conf + - /app/conf/etc/server.conf +``` +{% endraw %} + +{% raw %} +In this case, the Jinja expression `{{item}}` must be escaped: +{% endraw %} + +{% raw %} +```yaml +# Escape {{ only. +src: {{"{{"}} item }} +``` +or +```yaml +# Escape the whole expression. +src: {{`{{item}}`}} +``` +{% endraw %} + +### Ansible complications + +- Only raw and command modules support Live stdout output. Other modules display contents of stdout and stderr streams after execution. +- The `apt` module hangs the build process in some debian and ubuntu versions. The derived images are affected as well ([issue #645](https://github.com/werf/werf/issues/645)). + +## Environment variables of build container + +You can use service environment variables which are available in build container during the build. They can be used in your shell assembly instructions. Using them will not change the build instructions thus will not trigger stage rebuilds, even when these service environment variables change. + +Following environment variables are available: +- `WERF_COMMIT_HASH`. Example of value: `cda9d17265d174c62424e8f7b5e5640bf749c565`. +- `WERF_COMMIT_TIME_HUMAN`. Example of value: `2022-01-24 17:26:19 +0300 +0300`. +- `WERF_COMMIT_TIME_UNIX`. Example of value: `1643034379`. + +Usage example: +{% raw %} +```yaml +shell: + install: + - echo "Commands on the Install stage for $WERF_COMMIT_HASH" +``` +{% endraw %} + +In the example above the hash of the current commit will be added to the `echo ...` command, but this will happen in the very last moment — when the build instructions will be interpreted and executed by the shell. This way there will be no "install" stage rebuilds on every commit. + +## Dependencies of user stages + +werf features the ability to define dependencies for rebuilding the _stage_. As described in the [_stages_ reference]({{ "usage/build/stages_and_storage.html" | true_relative_url }}), _stages_ are built one by one, and the _digest_ is calculated for each _stage_. _Digests_ have various dependencies. When dependencies change, the _stage digest_ changes as well. As a result, werf rebuilds this _stage_ and all the subsequent _stages_. + +You can use these dependencies to shape the rebuilding process of _user stages_. _Digests_ of user stages (and, therefore, the rebuilding process) depend on: + +- changes in assembly instructions +- changes of _cacheVersion directives_ +- changes in the git repository +- changes in files being imported from [artifacts]({{ "usage/build_draft/stapel/imports.html#what-is-an-artifact" | true_relative_url }}) + +The first three dependencies are described below in more detail. + +## Dependency on changes in assembly instructions + +The _digest_ of the user stage depends on the rendered text of assembly instructions. Changes in assembly instructions for the _user stage_ lead to the rebuilding of this _stage_. Say, you have the following _shell-based assembly instructions_: + +```yaml +shell: + beforeInstall: + - echo "Commands on the Before Install stage" + install: + - echo "Commands on the Install stage" + beforeSetup: + - echo "Commands on the Before Setup stage" + setup: + - echo "Commands on the Setup stage" +``` + +On the first build of this image, instructions for all four user stages will be executed. There is no _git mapping_ in this _config_, so assembly instructions will never be executed on subsequent builds since _digests_ of user stages will be the same, and the build cache will remain valid. + +Let us change assembly instructions for the _install_ user stage: + +```yaml +shell: + beforeInstall: + - echo "Commands on the Before Install stage" + install: + - echo "Commands on the Install stage" + - echo "Installing ..." + beforeSetup: + - echo "Commands on the Before Setup stage" + setup: + - echo "Commands on the Setup stage" +``` + +The digest of the install stage has changed, so `werf build` executes assembly instructions in the _install_ stage and instructions defined in subsequent _stages_, i.e., _beforeSetup_ and _setup_. + +The stage digest may also change due to the use of environment variables and Go templates and that can lead to unforeseen rebuilds. For example: + +{% raw %} +```yaml +shell: + beforeInstall: + - echo "Commands on the Before Install stage for {{ env "CI_COMMIT_SHA” }}" + install: + - echo "Commands on the Install stage" + ... +``` +{% endraw %} + +The first build will calculate the _digest_ of the _beforeInstall_ stage: + +```shell +echo "Commands on the Before Install stage for 0a8463e2ed7e7f1aa015f55a8e8730752206311b" +``` + +The _digest_ of the _beforeInstall_ stage will change with each subsequent commit: + +```shell +echo "Commands on the Before Install stage for 36e907f8b6a639bd99b4ea812dae7a290e84df27" +``` + +In other words, the contents of assembly instructions will change with each subsequent commit because of the `CI_COMMIT_SHA` variable. Thus, such a configuration leads to the rebuild of the _beforeInstall_ user stage on every commit. + +## Dependency on changes in the git repo + + + Dependency on git repo changes + + +The _git mapping reference_ states that there are _gitArchive_ and _gitLatestPatch_ stages. _gitArchive_ runs after the _beforeInstall_ user stage, and _gitLatestPatch_ runs after the _setup_ user stage if there are changes in the local git repository. Thus, in order to execute assembly instructions using the latest version of the source code, you can initiate the rebuilding of the _beforeInstall_ stage (by changing _cacheVersion_ or its instructions). + +_install_, _beforeSetup_, and _setup_ user stages also depend on changes in the git repository. In this case, a git patch is applied at the beginning of the _user stage_, and assembly instructions are executed using the latest version of the source code. + +> During the process of building an image, the source code is updated **only at one of the stages**; all subsequent stages are based on this stage and thus use the actualized files. The source files contained in the git repository are added with the first build during the _gitArchive_ stage. All subsequent builds update source files during _gitCache_, _gitLatestPatch_ stages, or during one of the following user stages: _install_, _beforeSetup_, _setup_. + +
+
+This stage is pictured on the _Calculation digest phase_ +![git files actualized on specific stage]({{ "images/build/git_mapping_updated_on_stage.png" | true_relative_url }}) + +You can specify the dependency of the _user stage_ on changes in the git repository via the `git.stageDependencies` parameter. It has the following syntax: + +```yaml +git: +- ... + stageDependencies: + install: + - + ... + - + beforeSetup: + - + ... + setup: + - +``` + +The `git.stageDependencies` parameter has 3 keys: `install`, `beforeSetup` and `setup`. Each key defines an array of masks for a single user stage. The _user stage_ will be rebuilt if there are changes in the git repository that match one of the masks defined for the _user stage_. + +For each _user stage_, werf creates a list of matching files and calculates a checksum over attributes and contents of each file. This checksum is a part of the _stage digest_. Thus, the digest changes in response to any changes in the repository, such as getting new attributes for the file, changing its contents, adding new matching file, deleting a matching file, etc. + +`git.stageDependencies` masks work jointly with `git.includePaths` and `git.excludePaths` masks. Only files that match the `includePaths` filter and `stageDependencies` masks are considered suitable. Similarly, only files that do not match the `excludePaths` filter and `stageDependencies` masks are considered suitable by werf. + +`stageDependencies` masks work similarly to `includePaths` and `excludePaths` filters. The mask defines a template for files and paths and may contain the following glob patterns: + +- `*` — matches any file. This pattern includes `.` and excludes `/` +- `**` — matches directories recursively or files expansively +- `?` — matches any single character. It is equivalent to /.{1}/ in regexp +- `[set]` — matches any character within the set. It behaves exactly like character sets in regexp, including set negation ([^a-z]) +- `\` — escapes the next metacharacter + +Mask that starts with `*` is treated as an anchor name by the yaml parser. Thus, masks starting with `*` or `**` patterns at the beginning must be surrounded by quotation marks: + +```yaml +# * at the beginning of mask, so use double quotation marks +- "*.rb" +# single quotation marks also work +- '**/*' +# no star at the beginning, no quotation marks are needed +- src/**/*.js +``` + +werf finds out whether files have been changed in the git repository by calculating checksums. It applies the following algorithm for the _user stage_ and for each mask: + +- create a list of all files at the `add` path and apply the `excludePaths` and `includePaths` filters: +- compare path of each file in the list to the mask using of glob patterns; +- if some directory matches a mask, then all contents of this directory are considered matching recursively; +- calculate the checksum of attributes and contents of all matching files. + +These checksums are calculated at the beginning of the build process before any stage container is being run. + +Example: + +```yaml +image: app +git: +- add: /src + to: /app + stageDependencies: + beforeSetup: + - "*" +shell: + install: + - echo "install stage" + beforeSetup: + - echo "beforeSetup stage" + setup: + - echo "setup stage" +``` + +The _git mapping configuration_ in the above `werf.yaml` requires werf to transfer the contents of the `/src` directory of the local git repository to the `/app` directory of the image. During the first build, files are cached at the _gitArchive_ stage, and assembly instructions for _install_ and _beforeSetup_ are executed. During the builds triggered by the subsequent commits that do not change he contents of the `/src` directory, werf does not execute assembly instructions. If there were changes in the `/src` directory because of some commit, then checksums of files matching the mask would change. As a result, werf would apply the git patch and rebuild all the existing stages beginning with _beforeSetup_, namely _beforeSetup_ and _setup_. The git patch will be applied once during the _beforeSetup_ stage. + +## Dependency on the CacheVersion value + +There are situations when a user wants to rebuild all or just one _user stage_. This +can be accomplished by changing `cacheVersion` or `CacheVersion` values. + +The digest of the _install_ user stage depends on the value of the +`installCacheVersion` parameter. To rebuild the _install_ user stage (and +subsequent stages), you need to change the value of the `installCacheVersion` parameter. + +> Note that `cacheVersion` and `beforeInstallCacheVersion` directives have the same effect. Changing them triggers the rebuild of the _beforeInstall_ stage and all subsequent stages. + +### Example. The universal image for multiple applications + +An image containing shared system packages can be defined in a separate `werf.yaml` file. You can use the `cacheVersion` value for rebuilding this image to refresh packages’ versions. + +```yaml +image: ~ +from: ubuntu:latest +shell: + beforeInstallCacheVersion: 2 + beforeInstall: + - apt update + - apt install ... +``` + +You can use this image as a base for multiple applications if images from _hub.docker.com_ do not quite suit your needs. + +### Example of using external dependencies + +You can use _CacheVersion directives_ jointly with [go templates]({{ "reference/werf_yaml.html#image-section" | true_relative_url }}) to define dependency of the _user stage_ on files outside the git tree. + +{% raw %} +```yaml +image: ~ +from: ubuntu:latest +shell: + installCacheVersion: {{.Files.Get "some-library-latest.tar.gz" | sha256sum}} + install: + - tar zxf some-library-latest.tar.gz + - +``` +{% endraw %} + +The build script can be used to download `some-library-latest.tar.gz` archive and then execute the `werf build` command. Any changes to the file trigger the rebuild of the _install user stage_ and all the subsequent stages. diff --git a/docs/pages_en/usage/build_draft/stapel/mounts.md b/docs/pages_en/usage/build_draft/stapel/mounts.md new file mode 100644 index 0000000000..95d23156a2 --- /dev/null +++ b/docs/pages_en/usage/build_draft/stapel/mounts.md @@ -0,0 +1,39 @@ +--- +title: Reducing image size and speeding up a build by mounts +permalink: usage/build_draft/stapel/mounts.html +author: Artem Kladov , Alexey Igrychev +directive_summary: mount +--- + +Quite often when you build an image, you have auxiliary files that should be excluded from the image: +- Most package managers create a system-wide cache of packages and other files. + - [APT](https://wiki.debian.org/Apt) saves the package list in the `/var/lib/apt/lists/` directory. + - APT also saves packages in the `/var/cache/apt/` directory when installs them. + - [YUM](http://yum.baseurl.org/) can leave downloaded packages in the `/var/cache/yum/.../packages/` directory. +- Package managers for programming languages like ​npm (nodejs), glide (go), pip (python) store files in their cache folder. +- Building C, C++ and similar applications leaves behind itself the object files and other files of the used build systems. + +Thus, those files: +- are useless in the image; +- might enormously increase image size; +- might be useful in the next image builds. + +Reducing image size and speeding up a build can be performed by mounting external folders into assembly containers. Docker implements a mounting mechanism using [volumes](https://docs.docker.com/storage/volumes/). + +`mount` config directive is used for defining volumes. Host and assembly container mount folders determine each volume (accordingly in `from`/`fromPath` and `to` directives). +When specifying the host mount point, you can choose an arbitrary file or folder, defined in `fromPath`, or one of the service folders, defined in `from`: +- `tmp_dir` is an individual temporary image directory, created new for each build; +- `build_dir` is a collectively shared directory, stored between builds (`~/.werf/shared_context/mounts/projects///`). +Project images can use this common directory to share and store assembly data (e.g., cache). + +> werf binds host mount folders for reading/writing on each stage build. +If you need to keep assembly data from these directories in an image, you should copy them to another directory during build + +On `from` stage werf adds mount points definitions to stage image labels. +Then each stage uses these definitions for adding volumes to an assembly container. +The implementation allows inheriting mount points from [base image]({{ "usage/build_draft/stapel/base.html" | true_relative_url }}). + +Also, on `from` stage werf cleans assembly container mount points in a [base image]({{ "usage/build_draft/stapel/base.html" | true_relative_url }}). +Therefore, these folders are empty in an image. + +> By default, the use of the `fromPath` directive and `from: build_dir` are not allowed by giterminism (read more about it [here]({{ "/usage/project_configuration/giterminism.html#mount" | true_relative_url }})) diff --git a/docs/pages_en/usage/build_draft/stapel/overview.md b/docs/pages_en/usage/build_draft/stapel/overview.md new file mode 100644 index 0000000000..900490e389 --- /dev/null +++ b/docs/pages_en/usage/build_draft/stapel/overview.md @@ -0,0 +1,6 @@ +--- +title: Overview +permalink: usage/build_draft/stapel/overview.html +--- + + diff --git a/docs/pages_ru/usage/build_draft/images.md b/docs/pages_ru/usage/build_draft/images.md index 3da652b311..c2bdc26136 100644 --- a/docs/pages_ru/usage/build_draft/images.md +++ b/docs/pages_ru/usage/build_draft/images.md @@ -163,7 +163,7 @@ git: to: /app ``` -Доступно 4 стадии для описания произвольных shell-инструкций, а также директива `git.stageDependencies` для настройки триггеров пересборки этих стадий при изменении соответствующих стадий (см. [подробнее]({{ "/usage/build/building_images_with_stapel/assembly_instructions.html#зависимость-от-изменений-в-git-репозитории" | true_relative_url }})): +Доступно 4 стадии для описания произвольных shell-инструкций, а также директива `git.stageDependencies` для настройки триггеров пересборки этих стадий при изменении соответствующих стадий (см. [подробнее]({{ "/usage/build_draft/stapel/instructions.html#зависимость-от-изменений-в-git-репозитории" | true_relative_url }})): ```yaml image: app diff --git a/docs/pages_ru/usage/build_draft/stapel.md b/docs/pages_ru/usage/build_draft/stapel.md deleted file mode 100644 index bb48eceb7b..0000000000 --- a/docs/pages_ru/usage/build_draft/stapel.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Stapel -permalink: usage/build_draft/stapel.html ---- - - diff --git a/docs/pages_ru/usage/build_draft/stapel/base.md b/docs/pages_ru/usage/build_draft/stapel/base.md new file mode 100644 index 0000000000..a9b823c6a5 --- /dev/null +++ b/docs/pages_ru/usage/build_draft/stapel/base.md @@ -0,0 +1,74 @@ +--- +title: Базовый образ +permalink: usage/build_draft/stapel/base.html +author: Alexey Igrychev +directive_summary: base_image +--- + +Пример минимального `werf.yaml`: +```yaml +project: my-project +configVersion: 1 +--- +image: example +from: alpine +``` + +Приведенная конфигурация описывает _образ_ `example`, _базовым образом_ для которого является образ с именем `alpine`. + +_Базовый образ_ может быть указан с помощью директив `from`, `fromImage` или `fromArtifact`. + +## from, fromLatest + +Директива `from` определяет имя и тег _базового образа_. Если тег не указан, то по умолчанию — `latest`. + +```yaml +from: [:] +``` + +По умолчанию процесс сборки не зависит от digest _базового образа_, а зависит только от значения директивы `from`. +Поэтому изменение _базового образа_ в локальном хранилище или в container registry не будет влиять на сборку, пока стадия _from_, с указанным значением образа, находится в _stages storage_. + +Если вам нужна проверка digest образа, чтобы всегда использовать актуальный _базовый образ_, вы можете использовать директиву `fromLatest`. +Это приведет к тому, что при каждом запуске werf будет проверяться актуальный digest _базового образа_ в container registry. + +Пример использования директивы `fromLatest`: + +```yaml +fromLatest: true +``` + +> По умолчанию, использование директивы `fromLatest` запрещено гитерминизмом (подробнее об этом в [статье]({{ "usage/project_configuration/giterminism.html" | true_relative_url }})) + +## fromImage и fromArtifact + +В качестве _базового образа_ можно указывать не только образ из локального хранилища или container registry, но и имя другого _образа_ или [_артефакта_]({{ "usage/build_draft/stapel/imports.html#что-такое-артефакты" | true_relative_url }}), описанного в том же файле `werf.yaml`. В этом случае необходимо использовать директивы `fromImage` и `fromArtifact` соответственно. + +```yaml +fromImage: +fromArtifact: +``` + +Если _базовый образ_ уникален для конкретного приложения, то рекомендуемый способ — хранить его описание в конфигурации приложения (в файле `werf.yaml`) как отдельный _образ_ или _артефакт_, вместо того, чтобы ссылаться на Docker-образ. + +Также эта рекомендация будет полезной, если вам, по каким-либо причинам, не хватает существующего _конвейера стадий_. +Используя в качестве _базового образа_ образ, описанный в том же `werf.yaml`, вы по сути можете построить свой _конвейер стадий_. + + + + + +## fromCacheVersion + +Как описано выше, в обычном случае процесс сборки активно использует кэширование. +При сборке выполняется проверка — изменился ли _базовый образ_. +В зависимости от используемых директив эта проверка на изменение digest или имени и тега образа. +Если образ не изменился, то дайджест стадии `from` остается прежней, и если в _stages storage_ есть образ с таким дайджестом, то он и будет использован при сборке. + +С помощью директивы `fromCacheVersion` вы можете влиять на дайджест стадии `from` (т.к. значение `fromCacheVersion` — это часть дайджеста стадии) и, таким образом, управлять принудительной пересборкой образа. +Если вы измените значение, указанное в директиве `fromCacheVersion`, то независимо от того, менялся _базовый образ_ (или его digest) или остался прежним, при сборке изменится дайджест стадии `from` и, соответственно, всех последующих стадий. +Это приведет к тому, что сборка всех стадий будет выполнена повторно. + +```yaml +fromCacheVersion: +``` diff --git a/docs/pages_ru/usage/build_draft/stapel/dockerfile.md b/docs/pages_ru/usage/build_draft/stapel/dockerfile.md new file mode 100644 index 0000000000..8f28884a1d --- /dev/null +++ b/docs/pages_ru/usage/build_draft/stapel/dockerfile.md @@ -0,0 +1,38 @@ +--- +title: Использование Docker-инструкций +permalink: usage/build_draft/stapel/dockerfile.html +author: Alexey Igrychev +directive_summary: docker +--- + +Инструкции в [Dockerfile](https://docs.docker.com/engine/reference/builder/) можно условно разделить на две группы: сборочные инструкции и инструкции, которые влияют на manifest Docker-образа. +Так как werf сборщик использует свой синтаксис для описания сборки, поддерживаются только следующие Dockerfile-инструкции второй группы: + +* `USER` — имя пользователя (или UID) и опционально пользовательская группа (или GID) ([подробнее](https://docs.docker.com/engine/reference/builder/#user)) +* `WORKDIR` — рабочая директория ([подробнее](https://docs.docker.com/engine/reference/builder/#workdir)) +* `VOLUME` — точка монтирования ([подробнее](https://docs.docker.com/engine/reference/builder/#volume)) +* `ENV` — переменные окружения ([подробнее](https://docs.docker.com/engine/reference/builder/#env)) +* `LABEL` — метаданные ([подробнее](https://docs.docker.com/engine/reference/builder/#label)) +* `EXPOSE` — описание сетевых портов, которые будут прослушиваться в запущенном контейнере ([подробнее](https://docs.docker.com/engine/reference/builder/#expose)) +* `ENTRYPOINT` — команда по умолчанию, которая будет выполнена при запуске контейнера ([подробнее](https://docs.docker.com/engine/reference/builder/#entrypoint)) +* `CMD` — аргументы по умолчанию для `ENTRYPOINT` ([подробнее](https://docs.docker.com/engine/reference/builder/#cmd)) +* `HEALTHCHECK` — инструкции, которые Docker может использовать для проверки работоспособности запущенного контейнера ([подробнее](https://docs.docker.com/engine/reference/builder/#healthcheck)) + +Эти инструкции могут быть указаны с помощью директивы `docker` в конфигурации. + +Пример: + +```yaml +docker: + WORKDIR: /app + CMD: ["python", "./index.py"] + EXPOSE: '5000' + ENV: + TERM: xterm + LC_ALL: en_US.UTF-8 +``` + +Указанные в конфигурации Docker-инструкции применяются на последней стадии конвейера стадий, стадии `docker_instructions`. +Поэтому указание Docker-инструкций в `werf.yaml` никак не влияет на сам процесс сборки, а только добавляет данные к уже собранному образу. + +Если вам требуются определённые переменные окружения во время сборки (например, `TERM`), то вам необходимо использовать [базовый образ]({{ "usage/build_draft/stapel/base.html" | true_relative_url }}), в котором эти переменные окружения установлен или экспортировать их в [_пользовательской стадии_]({{ "usage/build/building_images_with_stapel/assembly_instructions.html#пользовательские-стадии" | true_relative_url }}). diff --git a/docs/pages_ru/usage/build_draft/stapel/git.md b/docs/pages_ru/usage/build_draft/stapel/git.md new file mode 100644 index 0000000000..1115426929 --- /dev/null +++ b/docs/pages_ru/usage/build_draft/stapel/git.md @@ -0,0 +1,324 @@ +--- +title: Добавление исходного кода из git-репозиториев +permalink: usage/build_draft/stapel/git.html +directive_summary: git +--- + +## Что такое git mapping? + +***Git mapping*** определяет, какой файл или папка из Git-репозитория должны быть добавлены в конкретное место образа. +Git-репозиторий может быть как локальным репозиторием, в котором находится файл конфигурации сборки (`werf.yaml`), так и удаленным. +В этом случае указывается адрес репозитория и версия кода — ветка, тег или конкретный коммит. + +werf добавляет файлы из Git-репозитория в образ, копируя их с помощью [git archive](https://git-scm.com/docs/git-archive), либо накладывая Git-патч. +При повторных сборках и появлении изменений в Git-репозитории werf добавляет patch к собранному ранее образу, чтобы в конечном образе отразить изменения файлов и папок. Более подробно механизм переноса файлов и накладывания патчей рассматриваются [в следующей секции](#подробнее-про-gitarchive-gitcache-gitlatestpatch). + +Конфигурация _git mapping_ поддерживает фильтры, что позволяет сформировать практически любую файловую структуру в образе, используя произвольное количество _git mappings_. +Также вы можете указать группу и владельца конечных файлов в образе, что освобождает от необходимости делать это отдельной командой (`chown`). + +В werf реализована поддержка сабмодулей Git (git submodules) и если werf определяет, что какая-то часть _git mapping_ является сабмодулем, то принимаются соответствующие меры, чтобы обрабатывать изменения в сабмодулях корректно. + +> Все Git-сабмодули проекта связаны с конкретным коммитом, поэтому все разработчики, работающие с репозиторием с сабмодулями, получают одинаковое содержимое. +> werf не инициализирует и не обновляет сабмодули, а использует соответствующие связанные коммиты. + +Пример добавления файлов из папки `/src` локального Git-репозитория в папку `/app` собираемого образа и добавления кода PhantomJS из удаленного репозитория в папку `/src/phantomjs` собираемого образа: + +```yaml +git: +- add: /src + to: /app +- url: https://github.com/ariya/phantomjs + add: / + to: /src/phantomjs +``` + +## Синтаксис + +Для добавления кода из локального Git-репозитория используется следующий синтаксис: + +- `add` — (не обязательный параметр) путь к директории или файлу, содержимое которого (которой) нужно добавить в образ. Указывается абсолютный путь *относительно корня* репозитория, т.е. он должен начинаться с `/`. По умолчанию копируется все содержимое репозитория, отсутствие параметра `add` равносильно указанию `add: /`; +- `to` — путь внутри образа, куда будет скопировано соответствующее содержимое; +- `owner` — имя или id пользователя-владельца файлов в образе; +- `group` — имя или id группы-владельца файлов в образе; +- `excludePaths` — список исключений (маска) при рекурсивном копировании файлов и папок. Указывается относительно пути, указанного в `add`; +- `includePaths` — список масок файлов и папок для рекурсивного копирования. Указывается относительно пути, указанного в `add`; +- `stageDependencies` — список масок файлов и папок для указания зависимости пересборки стадии от их изменений. Позволяет указать, при изменении каких файлов и папок необходимо принудительно пересобирать конкретную пользовательскую стадию. Более подробно рассматривается [здесь]({{ "usage/build_draft/stapel/instructions.html" | true_relative_url }}). + +При использовании удаленных репозиториев дополнительно используются следующие параметры: +- `url` — адрес удаленного репозитория; +- `branch`, `tag`, `commit` — имя ветки, тега или коммита соответственно. По умолчанию — ветка master. + +> По умолчанию использование директивы `branch` запрещено гитерминизмом (подробнее об этом [в статье]({{ "usage/project_configuration/giterminism.html" | true_relative_url }})). + +## Использование git mapping + +### Копирование директорий + +Параметр `add` определяет источник, путь в Git-репозитории, откуда файлы рекурсивно копируются в образ и помещаются по адресу, указанному в параметре `to`. Если параметр не определен, то по умолчанию используется значение `/`, т.е. копируется весь репозиторий. +Пример простейшей конфигурации, добавляющей содержимое всего локального Git-репозитория в образ в папку `/app`. + +```yaml +git: +- add: / + to: /app +``` + + +
+ git repository files tree +
+
+ image files tree +
+ +Также можно указать несколько _git mappings_: + +```yaml +git: +- add: /src + to: /app/src +- add: /assets + to: /static +``` + + +
+ git repository files tree +
+
+ image files tree +
+ +Следует отметить, что конфигурация _git mapping_ не похожа, например, на копирование типа `cp -r /src /app`. +Параметр `add` указывает на *содержимое* каталога, которое будет рекурсивно копироваться из репозитория. +Поэтому, если папка `/assets` со всем содержимым из репозитория должна быть скопирована в папку `/app/assets` образа, то имя *assets* вы должны указать два раза. +Либо, как вариант, вы можете использовать [фильтр](#использование-фильтров) (например, параметр `includePaths`). + +Примеры обоих вариантов, которые вы можете использовать для достижения одинакового результата: +```yaml +git: +- add: /assets + to: /app/assets +``` + +либо + +```yaml +git: +- add: / + to: /app + includePaths: assets +``` + +### Изменение владельца + +При добавлении файла из Git-репозитория вы можете указать имя и/или группу владельца файлов в образе. +Добавляемым файлам и папкам в образе при копировании будут установлены соответствующие права. +Пользователь и группа могут быть указаны как именем, так и числовым id (userid, groupid). + +Пример использования: + +```yaml +git: +- add: /src/index.php + to: /app/index.php + owner: www-data +``` + +Если указан только параметр `owner`, как в приведенном примере, то группой-владельцем устанавливается основная группа указанного пользователя в системе. + +В результате в папку `/app` образа будет добавлен файл `index.php` и ему будут установлены следующие права: + +![index.php owned by www-data user and group]({{ "images/build/git_mapping_05.png" | true_relative_url }}) + +Если значения параметра `owner` или `group` не числовые id, а текстовые (т.е. названия соответственно пользователя и группы), то соответствующие пользователь и группа должны существовать в системе. Их нужно добавить заранее при необходимости (к примеру, на стадии _beforeInstall_), иначе при сборке возникнет ошибка. + +```yaml +git: +- add: /src/index.php + to: /app/index.php + owner: wwwdata +``` + +### Использование фильтров + +Парамеры фильтров, `includePaths` и `excludePaths`, используются при составлении списка файлов для добавления. +Эти параметры содержат набор путей или масок, применяемых соответственно для включения и исключения файлов и папок при добавлении в образ. + +Фильтр `excludePaths` работает следующим образом: каждая маска списка применяется к каждому файлу, найденному по пути `add`. +Если файл удовлетворяет хотя бы одной маске, то он исключается из списка файлов на добавление. +Если файл не удовлетворяет ни одной маске, то он добавляется в образ. + +Фильтр `includePaths` работает наоборот — если файл удовлетворяет хотя бы одной маске, то он добавляется в образ. + +Конфигурация _git mapping_ может содержать оба фильтра. +В этом случае файл добавляется в образ, если его путь удовлетворяет хотя бы одной маске `includePaths` и не удовлетворяет ни одной маске `excludePaths`. + +Пример: + +```yaml +git: +- add: /src + to: /app + includePaths: + - '**/*.php' + - '**/*.js' + excludePaths: + - '**/*-dev.*' + - '**/*-test.*' +``` + +В приведенном примере добавляются `.php` и `.js` файлы из папки `/src` исключая файлы с суффиксом `-dev.` или `-test.` в имени файла. + +> Последний шаг в алгоритме с добавлением суффикса`**/*` сделан для удобства — вам достаточно указать название папки в параметрах _git mapping_, чтобы все ее содержимое удовлетворяло шаблону параметра + +Маска может содержать следующие шаблоны: + +- `*` — Удовлетворяет любому файлу. Шаблон включает `.` и исключает `/`. +- `**` — Удовлетворяет директории со всем ее содержимым, рекурсивно. +- `?` — Удовлетворяет любому **одному** символу в имени файла (аналогично regexp-шаблону `/.{1}/`). +- `[set]` — Удовлетворяет любому символу из указанного набора символов. Аналогично использованию в regexp-шаблонах, включая указание диапазонов типа `[^a-z]`. +- `\` — Экранирует следующий символ. + +Маска, которая начинается с шаблона `*` или `**`, должна быть взята в одинарные или двойные кавычки в `werf.yaml`: +- `"*.rb"` — двойные кавычки; +- `'**/*'` — одинарные кавычки. + +Примеры фильтров: + +```yaml +add: /src +to: /app +includePaths: +# Удовлетворяет всем php файлам, расположенным конкретно в папке /src. +- '*.php' + +# Удовлетворяет всем php файлам рекурсивно, начиная с папки /src +# (также удовлетворяет файлам *.php, т.к. '.' включается шаблон **). +- '**/*.php' + +# Удовлетворяет всем файлам в папке /src/module1 рекурсивно. +- module1 +``` + +Фильтр `includePaths` может применяться для копирования одного файла без изменения имени. Пример: +```yaml +git: +- add: /src + to: /app + includePaths: index.php +``` + +### Наложение путей копирования + +Если вы определяете несколько _git mappings_, вы должны учитывать, что при наложении путей в образе в параметре `to` вы можете столкнуться с невозможностью добавления файлов. Пример: + +```yaml +git: +- add: /src + to: /app +- add: /assets + to: /app/assets +``` + +Чтобы избежать ошибок сборки, werf определяет возможные наложения касающиеся фильтров `includePaths` и `excludePaths`, и если такое наложение присутствует, то werf пытается разрешить самые простые конфликты, неявно добавляя соответствующий параметр `excludePaths` в _git mapping_. +Однако, такое поведение может все-таки привести к неожиданным результатам, поэтому лучше всего избегать наложения путей при определении _git mappings_. + +В примере выше werf в итоге неявно добавит параметр `excludePaths`, и итоговая конфигурация будет следующей: + +```yaml +git: +- add: /src + to: /app + excludePaths: # werf добавил этот фильтр, чтобы исключить конфликт наложения результирующих путей + - assets # между /src/assets и /assets +- add: /assets + to: /app/assets +``` + +## Работа с удаленными репозиториями + +werf может использовать удаленные репозитории в качестве источника файлов. +Для указания адреса внешнего репозитория используется параметр `url`. +werf поддерживает работу с удаленными репозиториями по протоколам `https` и `git+ssh`. + +### HTTPS + +Синтаксис для работы по протоколу `https`: + +{% raw %} +```yaml +git: +- url: https://[USERNAME[:PASSWORD]@]repo_host/repo_path[.git/] +``` +{% endraw %} + +Указание логина и пароля при доступе по `https` опционально. + +Пример доступа к репозиторию из pipeline GitLab CI с использованием переменных окружения: + +{% raw %} +```yaml +git: +- url: https://{{ env "CI_REGISTRY_USER" }}:{{ env "CI_JOB_TOKEN" }}@registry.gitlab.company.name/common/helper-utils.git +``` +{% endraw %} + +В приведенном примере используется метод [env](http://masterminds.github.io/sprig/os.html) библиотеки [Sprig](http://masterminds.github.io/sprig/) для доступа к переменным окружения. + +### Git, SSH + +Доступ к удаленному репозиторию с помощью протокола `git` защищается с использованием доступа поверх SSH. Это распространенная практика, используемая в частности GitHub, Bitbucket, GitLab, Gogs, Gitolite и т.д. Обычно адрес репозитория выглядит следующим образом: + +```yaml +git: +- url: git@gitlab.company.name:project_group/project.git +``` + +Для работы с удаленными репозиториями по SSH необходимо понимать, как werf находит SSH-ключи (читай далее подробнее). + +#### Работа с SSH-ключами + +SSH-ключи для доступа предоставляются через SSH-agent. SSH-agent — это демон, который работает через файловый сокет, путь к которому хранится в переменной окружения `SSH_AUTH_SOCK`. werf монтирует этот файловый сокет во все _сборочные контейнеры_ и устанавливает переменную окружения `SSH_AUTH_SOCK`. Т.e. соединение с удаленным Git-репозиторием устанавливается с использованием ключей, зарегистрированных в запущенном SSH-агенте. + +werf использует следующий алгоритм для определения запущенного SSH-агента: + +- werf запущен с ключом `--ssh-key` (одним или несколькими): + - Запускается временный SSH-агент, в который добавляются указанные при запуске werf ключи. Эти ключи используются при всех операциях с удаленными репозиториями. + - Уже запущенный SSH-агент игнорируется. +- werf запущен без указания ключа `--ssh-key` и есть запущенный SSH-агент: + - Используется переменная окружения `SSH_AUTH_SOCK`, ключи добавляются в соответствующий SSH-агент и используются далее при всех операциях работы с удаленными репозиториями. +- werf запущен без указания ключа `--ssh-key` и нет запущенного SSH-агента: + - Если существует файл `~/.ssh/id_rsa`, запускается временный SSH-агент, в который добавляется ключ из файла `~/.ssh/id_rsa`. +- Если ни один из вариантов не применим, то SSH-агент не запускается и при операциях с внешними Git-репозиториями не используются никакие SSH-ключи. Сборка образа, с объявленными удаленными репозиториями в _git mapping_, завершится с ошибкой. + +## Подробнее про gitArchive, gitCache, gitLatestPatch + +Далее будет более подробно рассмотрен процесс добавления файлов в конечный образ. Как упоминалось ранее, Docker-образ состоит из набора слоёв. Чтобы понимать, какие слои создает werf, представим последовательную сборку трех коммитов: `1`, `2` и `3`: + +- Сборка коммита 1. Исходя из конфигурации _git mapping_, все соответствующие файлы добавляются в один слой. Сам процесс добавления выполняется с помощью `git archive`. Получившийся слой соответствует стадии _gitArchive_. +- Сборка коммита 2. Накладывается патч с изменениями файлов, в результате чего получается еще один слой. Получившийся слой соответствует стадии _gitLatestPatch_. +- Сборка коммита 3. Файлы уже добавлены, и werf накладывает патч, обновляя слой _gitLatestPatch_. + +Сборки для этих коммитов можно представить ​​следующим образом: + +| | gitArchive | gitLatestPatch | +| ------------------------------------ | :----------------------: | :----------------------: | +| Commit No. 1 is made, build at 10:00 | files as in commit No. 1 | - | +| Commit No. 2 is made, build at 10:05 | files as in commit No. 1 | files as in commit No. 2 | +| Commit No. 3 is made, build at 10:15 | files as in commit No. 1 | files as in commit No. 3 | + +Со временем количество коммитов растет, и размер патча между коммитом №1 и текущим может стать довольно большим. Это еще больше увеличит размер последнего слоя и общий размер стадий. Чтобы предотвратить неконтролируемый рост последнего слоя, werf предоставляет дополнительный промежуточный этап — _gitCache_. Когда _gitLatestPatch_ diff становится слишком большим, большая часть его diff объединяется с _gitCache_ diff, тем самым уменьшая размер стадии _gitLatestPatch_. + +### Rebase и _git-стадии_ + +Каждая Git-стадия хранит служебные лейблы с SHA коммитами, которые использовались при сборке стадии. +Эти коммиты будут использоваться при сборке следующей Git-стадии при создании патчей (по сути это `git diff COMMIT_FROM_PREVIOUS_GIT_STAGE LATEST_COMMIT` для каждого _git-mapping_). + +Если в стадии сохранён коммит, который отсутствует в Git-репозитории (например, после выполнения rebase), werf пересоберёт эту стадию, используя актуальный коммит. diff --git a/docs/pages_ru/usage/build_draft/stapel/imports.md b/docs/pages_ru/usage/build_draft/stapel/imports.md new file mode 100644 index 0000000000..7eee93cee1 --- /dev/null +++ b/docs/pages_ru/usage/build_draft/stapel/imports.md @@ -0,0 +1,175 @@ +--- +title: Импорт из артефактов и образов +permalink: usage/build_draft/stapel/imports.html +author: Alexey Igrychev +directive_summary: import +--- + +Из-за используемых инструментов сборки, либо просто из-за исходных файлов, размер конечного образа может увеличиваться в несколько раз. Зачастую эти файлы не нужны в конечном образе. Для решения таких проблем, сообщество Docker предлагает выполнять установки инструментов, сборку и удаление ненужных файлов за один шаг. + +Условный пример: +``` +RUN “download-source && cmd && cmd2 && remove-source” +``` + +> Аналогичный пример может быть реализован и в werf. Для этого достаточно описать инструкции в одной _пользовательской стадии_. Пример при использовании _shell-сборщика_ для стадии _install_ (аналогичен и для _ansible-сборщика_): +```yaml +shell: + install: + - "download-source" + - "cmd" + - "cmd2" + - "remove-source" +``` + +Однако при использовании такого метода кэширование работать не будет, и установка инструментов сборки будет выполняться каждый раз. + +Другой способ — использование multi-stage сборки, которая поддерживается начиная с версии 17.05 Docker. + +``` +FROM node:latest AS storefront +WORKDIR /app +COPY react-app . +RUN npm install +RUN npm run build + +FROM maven:latest AS appserver +WORKDIR /app +COPY . . +RUN mvn package + +FROM java:8-jdk-alpine +COPY --from=storefront /app/react-app/build/ /static +COPY --from=appserver /app/target/AtSea-0.0.1-SNAPSHOT.jar /app/AtSea.jar +``` + +Смысл такого подхода в следующем — описать несколько вспомогательных образов и выборочно копировать артефакты из одного образа в другой, оставляя все то, что не нужно в конечном образе. + +werf предлагает такой-же подход, но с использованием [_образов_]({{ "reference/werf_yaml.html#секция-image" | true_relative_url }}) и _артефактов_. + +> Почему werf не использует multi-stage сборку? +* Исторически, возможность _импорта_ появилась значительно раньше чем в Docker появилась multi-stage сборка. +* werf дает больше гибкости при работе со вспомогательными образами + +Импорт _ресурсов_ из _образов_ и _артефактов_ должен быть описан в директиве `import` в конфигурации [_образа_]({{ "reference/werf_yaml.html#секция-image" | true_relative_url }}) или _артефакта_ куда импортируются файлы. `import` — массив записей, каждая из которых должна содержать следующие параметры: + +- `image: ` или `artifact: `: _исходный образ_, имя образа из которого вы хотите копировать файлы или папки. +- `stage: `: _стадия исходного образа_, определённая стадия _исходного образа_ из которого вы хотите копировать файлы или папки. +- `add: `: _исходный путь_, абсолютный путь к файлу или папке в _исходном образе_ для копирования. +- `to: `: _путь назначения_, абсолютный путь в _образе назначения_ (куда импортируются файлы или папки). В случае отсутствия считается равным значению указанному в параметре `add`. +- `before: ` or `after: `: _стадия образа назначения_ для импорта. В настоящий момент возможен импорт только до/после стадии _install_ или _setup_. + +Пример: +```yaml +import: +- artifact: application-assets + add: /app/public/assets + to: /var/www/site/assets + after: install +- image: frontend + add: /app/assets + after: setup +``` + +Так же как и при конфигурации _git mappings_ поддерживаются маски включения и исключения файлов и папок. +Для указания маски включения файлов используется параметр `include_paths: []`, а для исключения `exclude_paths: []`. Маски указываются относительно пути источника (параметр `add`). +Вы также можете указывать владельца и группу для импортируемых ресурсов с помощью параметров `owner: ` и `group: ` соответственно. +Это поведение аналогично используемому при добавлении кода из git-репозиториев, и вы можете подробнее почитать об этом в [соответствующем разделе]({{ "usage/build_draft/stapel/git.html" | true_relative_url }}). + +> Обратите внимание, что путь импортируемых ресурсов и путь указанный в _git mappings_ не должны пересекаться + +## Что такое артефакты? + +***Артефакт*** — это специальный образ, используемый в других артефактах или отдельных образах, описанных в конфигурации. Артефакт предназначен преимущественно для отделения ресурсов инструментов сборки от процесса сборки образа приложения. Примерами таких ресурсов могут быть — программное обеспечение или данные, которые необходимы для сборки, но не нужны для запуска приложения, и т.п. + +Используя артефакты, вы можете собирать неограниченное количество компонентов, что позволяет решать, например, следующие задачи: +- Если приложение состоит из набора компонент, каждый со своими зависимостями, то обычно вам приходится пересобирать все компоненты каждый раз. Вам бы хотелось пересобирать только те компоненты, которым это действительно нужно. +- Компоненты должны быть собраны в разных окружениях. + +Импортирование _ресурсов_ из _артефактов_ указывается с помощью [директивы import]({{ "usage/build_draft/stapel/imports.html" | true_relative_url }}) в конфигурации в [_секции образа_]({{ "reference/werf_yaml.html#секция-image" | true_relative_url }}) или _секции артефакта_. + +### Конфигурация + +Конфигурация _артефакта_ похожа на конфигурацию обычного _образа_. Каждый _артефакт_ должен быть описан в своей секции конфигурации. + +Инструкции, связанные со стадией _from_ (инструкции указания [базового образа]({{ "usage/build_draft/stapel/base.html" | true_relative_url }}) и [монтирования]({{ "usage/build_draft/stapel/mounts.html" | true_relative_url }})), а также инструкции [импорта]({{ "usage/build_draft/stapel/imports.html" | true_relative_url }}) точно такие же как и при описании _образа_. + +Стадия добавления инструкций Docker (`docker_instructions`) и [соответствующие директивы]({{ "usage/build_draft/stapel/dockerfile.html" | true_relative_url }}) не доступны при описании _артефактов_. _Артефакт_ — это инструмент сборки, и все что от него требуется, это — только данные. + +Остальные _стадии_ и инструкции описания артефактов рассматриваются далее подробно. + +#### Именование + +
+```yaml +artifact: string +``` +
+ +_Образ артефакта_ объявляется с помощью директивы `artifact`. Синтаксис: `artifact: string`. Так как артефакты используются только самим werf, отсутствуют какие-либо ограничения на именование артефактов, в отличие от ограничений на [именование обычных _образов_]({{ "reference/werf_yaml.html#секция-image" | true_relative_url }}). + +Пример: +```yaml +artifact: "application assets" +``` + +#### Добавление исходного кода из git-репозиториев + +
+ + + + + +
+ +В отличие от обычных _образов_, у _конвейера стадий артефактов_ нет стадий _gitCache_ и _gitLatestPatch_. + +> В werf для _артефактов_ реализована необязательная зависимость от изменений в git-репозиториях. Таким образом, по умолчанию werf игнорирует какие-либо изменения в git-репозитории, кэшируя образ после первой сборки. Но вы можете определить зависимости от файлов и папок, при изменении в которых образ артефакта будет пересобираться + +Читайте подробнее про работу с _git-репозиториями_ в соответствующей [статье]({{ "usage/build_draft/stapel/git.html" | true_relative_url }}). + +#### Запуск инструкций сборки + +
+ + + + + +
+ +У артефактов точно такое же как и у обычных образов использование директив и пользовательских стадий — _beforeInstall_, _install_, _beforeSetup_ и _setup_. + +Если в директиве `stageDependencies` в блоке git для _пользовательской стадии_ не указана зависимость от каких-либо файлов, то образ кэшируется после первой сборки, и не будет повторно собираться пока соответствующая _стадия_ находится в _stages storage_. + +> Если необходимо повторно собирать артефакт при любых изменениях в git, нужно указать _stageDependency_ `**/*` для соответствующей _пользовательской_ стадии. Пример для стадии _install_: +```yaml +git: +- to: / + stageDependencies: + install: "**/*" +``` + +Читайте подробнее про работу с _инструкциями сборки_ в соответствующей [статье]({{ "usage/build_draft/stapel/instructions.html" | true_relative_url }}). + +## Использование артефактов + +В отличие от [*обычного образа*]({{ "usage/build_draft/stapel/instructions.html" | true_relative_url }}), у *образа артефакта* нет стадии _git latest patch_. Это сделано намеренно, т.к. стадия _git latest patch_ выполняется обычно при каждом коммите, применяя появившиеся изменения к файлам. Однако *артефакт* рекомендуется использовать как образ с высокой вероятностью кэширования, который обновляется редко или нечасто (например, при изменении специальных файлов). + +Пример: нужно импортировать в артефакт данные из git, и пересобирать ассеты только тогда, когда изменяются влияющие на сборку ассетов файлы. Т.е. в случае, изменения каких-либо других файлов в git, ассеты пересобираться не должны. + +Конечно, существуют случаи когда необходимо включать изменения любых файлов git-репозитория в _образ артефакта_ (например, если в артефакте происходит сборка приложения на Go). В этом случае необходимо указать зависимость относительно стадии (сборку которой необходимо выполнять при изменениях в git) с помощью `git.stageDependencies` и `*` в качестве шаблона. Пример: + +```yaml +git: +- add: / + to: /app + stageDependencies: + setup: + - "*" +``` + +В этом случае любые изменения файлов в git-репозитории будут приводить к пересборке _образа артефакта_, и всех _образов_, в которых определен импорт этого артефакта. + +**Замечание:** Если вы используете какие-либо файлы и при сборке _артефакта_ и при сборке [*обычного образа*]({{ "usage/build_draft/stapel/instructions.html" | true_relative_url }}), правильный путь — использовать директиву `git.add` при описании каждого образа, где это необходимо, т.е. несколько раз. **Не рекомендуемый** вариант — добавить файлы при сборке артефакта, а потом импортировать их используя директиву `import` в другой образ. diff --git a/docs/pages_ru/usage/build_draft/stapel/instructions.md b/docs/pages_ru/usage/build_draft/stapel/instructions.md new file mode 100644 index 0000000000..c93a5ef20e --- /dev/null +++ b/docs/pages_ru/usage/build_draft/stapel/instructions.md @@ -0,0 +1,504 @@ +--- +title: Запуск инструкций сборки +permalink: usage/build_draft/stapel/instructions.html +directive_summary: shell_and_ansible +--- + +## Пользовательские стадии + +***Пользовательские стадии*** — это [_стадии_]({{ "usage/build/stages_and_storage.html" | true_relative_url }}) со сборочными инструкциями из [конфигурации]({{ "reference/werf_yaml.html#секция-image" | true_relative_url }}). Другими словами — это стадии, конфигурируемые пользователем (существуют также служебные стадии, которые пользователь конфигурировать не может). В настоящее время существует два вида сборочных инструкций: _shell_ и _ansible_. + +werf поддерживает четыре _пользовательские стадии_, которые выполняются последовательно в следующем порядке: _beforeInstall_, _install_, _beforeSetup_ и _setup_. В результате выполнения инструкций пользовательской стадии создается один Docker-слой. Т.е. по одному слою на каждую стадию вне зависимости от количества инструкций. + +## Использование пользовательских стадий + +werf позволяет определять до четырех _пользовательских стадий_ с инструкциями сборки. На содержание самих инструкций сборки werf не накладывает каких-либо ограничений, т.е. вы можете указывать все те же инструкции, которые указывали в Dockerfile в директиве `RUN`. Однако важно не просто перенести инструкции из Dockerfile, а правильно разбить их на _пользовательские стадии_. Исходя из опыта работы с реальными приложениями, мы пришли к заключению, что сборка большинства приложений проходит следующие этапы: + +- установка системных пакетов; +- установка системных зависимостей; +- установка зависимостей приложения; +- настройка системных пакетов; +- настройка приложения. + +Какая может быть наилучшая стратегия выполнения этих этапов? + +Первая мысль — лучше выполнять эти этапы последовательно, кэшируя промежуточные результаты, а с другой стороны – разбивать инструкции по файловым зависимостям. + +Подход с _пользовательскими стадиями_ предлагает следующую стратегию: +- использовать стадию _beforeInstall_ для инсталляции системных пакетов; +- использовать стадию _install_ для инсталляции системных зависимостей и зависимостей приложения; +- использовать стадию _beforeSetup_ для настройки системных параметров и установки приложения; +- использовать стадию _setup_ для настройки приложения. + +### beforeInstall + +Данная стадия предназначена для выполнения инструкций перед установкой приложения. +Этот этап предназначен для системных приложений, которые редко изменяются, но требуют много времени для установки. +Примером таких приложений могут быть языковые пакеты или инструменты сборки, такие как Composer, Java, Gradle и т.д. +Также сюда можно добавлять инструкции настройки системы, которые редко изменяются. +Например, языковые настройки, настройки часового пояса, добавление пользователей и групп. + +Поскольку эти компоненты меняются редко, они будут кэшироваться в рамках стадии _beforeInstall_ на длительный период. + +### install + +Данная стадия предназначена для установки приложения и его зависимостей, а также выполнения базовых настроек. + +На данной стадии появляется доступ к исходному коду используемых Git-репозиториев (директива `git`) и появляется возможность установки зависимостей на основе manifest-файлов с использованием таких инструментов, как Composer, Gradle, npm и т.д. Поскольку сборка стадии зависит от manifest-файла, для достижения наилучшего результата важно добавить зависимость от изменений в manifest-файлах репозитория для этой стадии. Например, если в проекте используется Composer, то добавление файла `composer.lock` в зависимости стадии _beforeInstall_ позволит пересобирать стадию при изменении файла `composer.lock`. + +### beforeSetup + +Данная стадия предназначена для подготовки приложения перед настройкой. + +На данной стадии рекомендуется выполнять разного рода компиляцию и обработку. +Например, компиляция jar-файлов, бинарных файлов, файлов библиотек, создание ассетов web-приложений, минификация, шифрование и т.п. +Перечисленные операции, как правило, зависят от изменений в исходном коде, и на данной стадии также важно определить достаточные зависимости от изменений в репозитории. Логично, что стадия будет зависеть от большего числа файлов в репозитории, чем на предыдущей стадии, и, соответственно, ее пересборка будет выполняться чаще. + +При правильно определенных зависимостях изменения в коде приложения должны приводить к пересборке стадии _beforeSetup_, а изменение manifest-файла к стадии _install_ и последующих стадий. + +### setup + +Данная стадия предназначена для настройки приложения. + +Обычно на данной стадии выполняется копирование файлов конфигурации (например, в папку `/etc`), создание файлов текущей версии приложения и т.д. +Такого рода операции не должны быть затратными по времени, т.к. они, скорее всего, будут выполняться в большинстве сборок. + +### Пользовательская стратегия + +Несмотря на изложенную четкую стратегию шаблона _пользовательских стадий_ и функции каждой стадии, по сути для пользователя нет никаких ограничений. Предложенное использование стадий является лишь рекомендацией, которая основана на нашем опыте с реальными приложениями. Вы можете использовать только одну пользовательскую стадию, либо определить свою стратегию группировки инструкций, чтобы получить преимущества кэширования и зависимостей от изменений в Git-репозиториях с учетом особенностей сборки вашего приложения. + +## Синтаксис + +Пользовательские стадии и инструкции сборки определяются внутри двух взаимоисключающих директив — `shell` и `ansible`. Каждый образ может собираться либо используя сборочные инструкции ***shell***, либо задачи ***ansible***, описанные в соответствующих директивах. + +В каждой директиве можно описывать инструкции для _пользовательских стадий_, соответственно: +- `beforeInstall`; +- `install`; +- `beforeSetup`; +- `setup`. + +Также можно указывать директивы версий кэша (***cacheVersion***), которые по сути являются частью дайджеста каждой _пользовательской стадии_. Более подробно [в соответствующем разделе](#зависимость-от-значения-cacheversion). + +## Shell + +Синтаксис описания _пользовательских стадий_ при использовании сборочных инструкций _shell_: + +```yaml +shell: + beforeInstall: + - + - + ... + - + install: + - bash command + ... + beforeSetup: + - bash command + ... + setup: + - bash command + ... + cacheVersion: + beforeInstallCacheVersion: + installCacheVersion: + beforeSetupCacheVersion: + setupCacheVersion: +``` + +Сборочные инструкции _shell_ — это массив Bash-команд для соответствующей _пользовательской стадии_. Все команды одной стадии выполняются как одна инструкция `RUN` в Dockerfile, т.е. в результате создается один слой на каждую _пользовательскую стадию_. + +werf при сборке использует собственный исполняемый файл `bash`, и вам не нужно отдельно добавлять его в образ (или [базовый образ]({{ "usage/build_draft/stapel/base.html" | true_relative_url }})) при сборке. + +Пример описания стадии _beforeInstall_ содержащей команды `apt-get update` и `apt-get install`: + +```yaml +beforeInstall: +- apt-get update +- apt-get install -y build-essential g++ libcurl4 +``` + +Исполняемый файл `bash` находится внутри Docker-тома _stapel_. Подробнее про эту концепцию можно узнать [в этой статье](https://habr.com/company/flant/blog/352432/) (упоминаемый в статье `dappdeps` был переименован в `stapel`, но принцип сохранился) + +## Ansible + +Синтаксис описания _пользовательских стадий_ при использовании сборочных инструкций _ansible_: + +```yaml +ansible: + beforeInstall: + - + - + ... + - + install: + - ansible task + ... + beforeSetup: + - ansible task + ... + setup: + - ansible task + ... + cacheVersion: + beforeInstallCacheVersion: + installCacheVersion: + beforeSetupCacheVersion: + setupCacheVersion: +``` + +### Ansible config и stage playbook + +Сборочные инструкции _ansible_ — это массив Ansible-заданий для соответствующей _пользовательской стадии_. + +Сгенерированный `ansible.cfg` содержит настройки для Ansible: +- использование локального транспорта (transport = local); +- подключение callback плагина werf для удобного логирования (stdout_callback = werf); +- включение режима цвета (force_color = 1); +- установка использования `sudo` для повышения привилегий (чтобы не было необходимости использовать `become` в Ansible-заданиях). + +Сгенерированный `playbook.yml` — playbook, содержащий все задания соответствующей _пользовательской стадии_. Пример `werf.yaml` с описанием стадии _install_: + +```yaml +ansible: + install: + - debug: msg='Start install' + - file: path=/etc mode=0777 + - copy: + src: /bin/sh + dest: /bin/sh.orig + - apk: + name: curl + update_cache: yes + ... +``` + +Исполняемые файлы и библиотеки `ansible` и `python` находятся внутри Docker-тома _stapel_. Подробнее про эту концепцию можно узнать [в этой статье](https://habr.com/company/flant/blog/352432/) (упоминаемый в статье `dappdeps` был переименован в `stapel`, но принцип сохранился) + +### Поддерживаемые модули + +Одной из концепций, которую использует werf, является идемпотентность сборки. Это значит, что если «ничего не изменилось», то werf при повторном и последующих запусках сборки должен создавать бинарно идентичные образы. В werf эта задача решается с помощью подсчета _дайджестов стадий_. + +Многие модули Ansible не являются идемпотентными, т.е. они могут давать разный результат запусков при неизменных входных параметрах. Это, конечно, не дает возможность корректно высчитывать _дайджест_ стадии, чтобы определять реальную необходимость её пересборки из-за изменений. Это привело к тому, что список поддерживаемых модулей был ограничен. + +На текущий момент, список поддерживаемых модулей Ansible следующий: + +- [Commands modules](https://docs.ansible.com/ansible/2.5/modules/list_of_commands_modules.html): command, shell, raw, script. +- [Crypto modules](https://docs.ansible.com/ansible/2.5/modules/list_of_crypto_modules.html): openssl_certificate и другие. +- [Files modules](https://docs.ansible.com/ansible/2.5/modules/list_of_files_modules.html): acl, archive, copy, stat, tempfile и другие. +- [Net Tools Modules](https://docs.ansible.com/ansible/2.5/modules/list_of_net_tools_modules.html): get_url, slurp, uri. +- [Packaging/Language modules](https://docs.ansible.com/ansible/2.5/modules/list_of_packaging_modules.html#language): composer, gem, npm, pip и другие. +- [Packaging/OS modules](https://docs.ansible.com/ansible/2.5/modules/list_of_packaging_modules.html#os): apt, apk, yum и другие. +- [System modules](https://docs.ansible.com/ansible/2.5/modules/list_of_system_modules.html): user, group, getent, locale_gen, timezone, cron и другие. +- [Utilities modules](https://docs.ansible.com/ansible/2.5/modules/list_of_utilities_modules.html): assert, debug, set_fact, wait_for. + +При указании в _конфигурации сборки_ модуля, отсутствующего в приведенном списке, сборка прервется с ошибкой. Не стесняйтесь [сообщать](https://github.com/werf/werf/issues/new) нам, если вы считаете что какой-либо модуль должен быть включен в список поддерживаемых. + +### Копирование файлов + +Предпочтительный способ копирования файлов в образ — использование [_git mapping_]({{ "usage/build_draft/stapel/git.html" | true_relative_url }}). +werf не может определять изменения в копируемых файлах при использовании модуля `copy`. +Единственный вариант копирования внешнего файла в образ на текущий момент — использовать метод `.Files.Get` Go-шаблона. +Данный метод возвращает содержимое файла как строку, что дает возможность использовать содержимое как часть _пользовательской стадии_. +Таким образом, при изменении содержимого файла изменится дайджест соответствующей стадии, что приведет к пересборке всей стадии. + +Пример копирования файла `nginx.conf` в образ: + +{% raw %} +```yaml +ansible: + install: + - copy: + content: | +{{ .Files.Get "/conf/etc/nginx.conf" | indent 8}} + dest: /etc/nginx/nginx.conf +``` +{% endraw %} + +В результате получится подобный `playbook.yml`: + +```yaml +- hosts: all + gather_facts: no + tasks: + install: + - copy: + content: | + http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + ... +``` + +### Шаблоны Jinja + +В Ansible реализована поддержка шаблонов [Jinja](https://docs.ansible.com/ansible/2.5/user_guide/playbooks_templating.html) в playbook'ах. Однако у Go-шаблонов и Jinja-шаблонов одинаковый разделитель: {% raw %}`{{` и `}}`{% endraw %}. Чтобы использовать Jinja-шаблоны в конфигурации werf, их нужно экранировать. Для этого есть два варианта: экранировать только {% raw %}`{{`{% endraw %}, либо экранировать все выражение шаблона Jinja. + +Например, у вас есть следующая задача Ansible: + +{% raw %} +```yaml +- copy: + src: {{item}} + dest: /etc/nginx + with_files: + - /app/conf/etc/nginx.conf + - /app/conf/etc/server.conf +``` +{% endraw %} + +{% raw %} +Тогда, выражение Jinja-шаблона `{{item}}` должно быть экранировано: +{% endraw %} + +{% raw %} +```yaml +# Экранируем только {{. +src: {{"{{"}} item }} +``` +либо +```yaml +# Экранируем все выражение. +src: {{`{{item}}`}} +``` +{% endraw %} + +### Проблемы с Ansible + +- Live-вывод реализован только для модулей `raw` и `command`. Остальные модули отображают вывод каналов `stdout` и `stderr` после выполнения, что приводит к задержкам и скачкообразному выводу. +- Модуль `apt` подвисает на некоторых версиях Debian и Ubuntu. Проявляется также на наследуемых образах ([issue #645](https://github.com/werf/werf/issues/645)). + +## Переменные окружения сборочного контейнера + +Вы можете использовать сервисные переменные окружения, которые доступны в сборочном контейнере, и, соответственно, доступны в инструкциях сборки. Их использование не приведёт к изменению инструкций сборки и вытекающим из этого пересборкам, даже если сами значения сервисных переменных меняются. + +Доступны следующие переменные: +- `WERF_COMMIT_HASH`. Пример значения: `cda9d17265d174c62424e8f7b5e5640bf749c565`. +- `WERF_COMMIT_TIME_HUMAN`. Пример значения: `2022-01-24 17:26:19 +0300 +0300`. +- `WERF_COMMIT_TIME_UNIX`. Пример значения: `1643034379`. + +Пример использования: +{% raw %} +```yaml +shell: + install: + - echo "Commands on the Install stage for $WERF_COMMIT_HASH" +``` +{% endraw %} + +В примере выше хэш текущего коммита будет подставлен в команду `echo ...`, но произойдет это в самый последний момент — на этапе выполнения инструкций shell'ом. Таким образом пересборок слоя install на каждом коммите происходить не будет. + +## Зависимости пользовательских стадий + +Одна из особенностей werf — возможность определять зависимости, при которых происходит пересборка _стадии_. +Как указано [в статье]({{ "usage/build/stages_and_storage.html" | true_relative_url }}), сборка _стадий_ выполняется последовательно, одна за другой, и для каждой _стадии_ высчитывается _дайджест стадии_. У _дайджестов_ есть ряд зависимостей, при изменении которых _дайджест стадии_ меняется, что служит для werf сигналом для пересборки стадии с измененным _дайджестом_. Поскольку каждая следующая _стадия_ имеет зависимость, в том числе и от предыдущей _стадии_ согласно _конвейеру стадий_, при изменении дайджеста какой-либо _стадии_ произойдет пересборка и _стадии_ с изменённым дайджестом и всех последующих _стадий_. + +_Дайджест пользовательских стадий_ и, соответственно, пересборка _пользовательских стадий_ зависят от изменений: +- в инструкциях сборки; +- в директивах семейства _cacheVersion_; +- в Git-репозитории (или Git-репозиториях); +- в файлах, импортируемых из [артефактов]({{ "usage/build_draft/stapel/imports.html#что-такое-артефакты" | true_relative_url }}). + +Первые три описанных варианта зависимостей рассматриваются подробно далее. + +## Зависимость от изменений в инструкциях сборки + +_Дайджест пользовательской стадии_ зависит от итогового текста инструкций, т.е. после применения шаблонизатора. Любые изменения в тексте инструкций с учетом применения шаблонизатора Go или Jinja (в случае Ansible) в _пользовательской стадии_ приводят к пересборке _стадии_. Например, вы используете следующие _shell-инструкции_: + +```yaml +shell: + beforeInstall: + - echo "Commands on the Before Install stage" + install: + - echo "Commands on the Install stage" + beforeSetup: + - echo "Commands on the Before Setup stage" + setup: + - echo "Commands on the Setup stage" +``` + +При первой сборке этого образа будут выполнены инструкции всех четырех _пользовательских стадий_. +В данной конфигурации нет _git mapping_, так что последующие сборки не приведут к повторному выполнению инструкций — _дайджест пользовательских стадий_ не изменилась, сборочный кэш содержит актуальную информацию. + +Изменим инструкцию сборки для стадии _install_: + +```yaml +shell: + beforeInstall: + - echo "Commands on the Before Install stage" + install: + - echo "Commands on the Install stage" + - echo "Installing ..." + beforeSetup: + - echo "Commands on the Before Setup stage" + setup: + - echo "Commands on the Setup stage" +``` + +Дайджест стадии _install_ изменилась, и запуск werf для сборки приведет к выполнению всех инструкций стадии _install_ и инструкций последующих _стадий_, т.е. _beforeSetup_ и _setup_. + +Дайджест стадии может меняться также из-за использования переменных окружения и Go-шаблонов. +Если не уделять этому достаточное внимание при написании конфигурации, можно столкнуться с неожиданными пересборками стадий. Например: + +{% raw %} +```yaml +shell: + beforeInstall: + - echo "Commands on the Before Install stage for {{ env "CI_COMMIT_SHA” }}" + install: + - echo "Commands on the Install stage" + ... +``` +{% endraw %} + +Первая сборка высчитает дайджест стадии _beforeInstall_ на основе команды (хэш коммита, конечно, будет другой): +```shell +echo "Commands on the Before Install stage for 0a8463e2ed7e7f1aa015f55a8e8730752206311b" +``` + +После очередного коммита, при сборке дайджест стадии _beforeInstall_ уже будет другой (с других хешем коммита): + +```shell +echo "Commands on the Before Install stage for 36e907f8b6a639bd99b4ea812dae7a290e84df27" +``` + +Соответственно, используя переменную `CI_COMMIT_SHA` дайджест стадии _beforeInstall_ будет меняться после каждого коммита, что будет приводить к пересборке. + +## Зависимость от изменений в Git-репозитории + + + Зависимость от изменений в git-репозитории + + +Как описывалось в статье [про _git mapping_]({{ "usage/build_draft/stapel/git.html" | true_relative_url }}), существуют специальные стадии _gitArchive_ и _gitLatestPatch_. +Стадия _gitArchive_ выполняется после пользовательской стадии _beforeInstall_, а стадия _gitLatestPatch_ после пользовательской стадии _setup_, если в локальном Git-репозитории есть изменения. +Таким образом, чтобы выполнить сборку с последней версией исходного кода, можно пересобрать стадию _beforeInstall_, изменив значение директивы _cacheVersion_ либо изменив сами инструкции стадии _beforeInstall_. + +Пользовательские стадии _install_, _beforeSetup_ и _setup_ также могут зависеть от изменений в Git-репозитории. В этом случае (если такая зависимость определена) Git-патч применяется перед выполнением инструкций _пользовательской стадии_, чтобы сборочные инструкции выполнялись с актуальной версией кода приложения. + +> Во время процесса сборки исходный код обновляется **только в рамках одной стадии**, последующие стадии, зависящие последовательно друг от друга, будут использовать также обновленную версию файлов. +> Первая сборка добавляет файлы из Git-репозитория на стадии _gitArchive_. Все последующие сборки обновляют файлы на стадии _gitCache_, _gitLatestPatch_ или на одной из следующих пользовательских стадий: _install_, _beforeSetup_, _setup_. +
+
+Пример этого этапа (фаза подсчета дайджестов, _calculating digests_): +![git files actualized on specific stage]({{ "images/build/git_mapping_updated_on_stage.png" | true_relative_url }}) + +Зависимость _пользовательской стадии_ от изменений в Git-репозитории указывается с помощью параметра `git.stageDependencies`. Синтаксис: + +```yaml +git: +- ... + stageDependencies: + install: + - + ... + - + beforeSetup: + - + ... + setup: + - +``` + +У параметра `git.stageDependencies` возможно указывать 3 ключа: `install`, `beforeSetup` и `setup`. +Значение каждого ключа — массив масок файлов, относящихся к соответствующей стадии. Соответствующая _пользовательская стадия_ пересобирается, если в Git-репозитории происходят изменения подпадающие под указанную маску. + +Для каждой _пользовательской стадии_ werf создает список подпадающих под маску файлов и вычисляет контрольную сумму каждого файла с учетом его аттрибутов и содержимого. Эти контрольные суммы являются частью _дайджеста стадии_, поэтому любое изменение файлов в репозитории, подпадающее под маску, приводит к изменениям _дайджеста стадии_. К этим изменениям относятся: изменение атрибутов файла, изменение содержимого файла, добавление или удаление подпадающего под маску файла и т.п. + +При применении маски, указанной в `git.stageDependencies`, учитываются значения параметров `git.includePaths` и `git.excludePaths` (смотри подробнее про них [в соответствующем разделе]({{ "usage/build_draft/stapel/git.html#использование-фильтров" | true_relative_url }})). werf считает подпадающими под маску только файлы, удовлетворяющие фильтру `includePaths` и подпадающие под маску `stageDependencies`. Аналогично werf считает подпадающими под маску только файлы, не удовлетворяющие фильтру `excludePaths` и не подпадающие под маску `stageDependencies`. + +Правила описания маски в параметре `stageDependencies` аналогичны описанию параметров `includePaths` и `excludePaths`. Маска определяет шаблон для файлов и путей и может содержать следующие шаблоны: + +- `*` — Удовлетворяет любому файлу. Шаблон включает `.` и исключает `/`. +- `**` — Удовлетворяет директории со всем ее содержимым, рекурсивно. +- `?` — Удовлетворяет любому одному символу в имени файла (аналогично regexp-шаблону `/.{1}/`). +- `[set]` — Удовлетворяет любому символу из указанного набора символов. Аналогично использованию в regexp-шаблонах, включая указание диапазонов типа `[^a-z]`. +- `\` — Экранирует следующий символ. + + +Маска, которая начинается с шаблона `*` или `**`, должна быть взята в одинарные или двойные кавычки в `werf.yaml`: + +``` +# * в начале маски, используем двойные кавычки +- "*.rb" +# одинарные также работают +- '**/*' +# нет * в начале, можно не использовать кавычки +- src/**/*.js +``` + +Факт изменения файлов в Git-репозитории werf определяет, подсчитывая их контрольные суммы. Для _пользовательской стадии_ и для каждой маски применяется следующий алгоритм: + +- werf создает список всех файлов согласно пути, определенному в параметре `add`, и применяет фильтры `excludePaths` и `includePaths`; +- К каждому файлу с учетом его пути применяется маска, согласно правилам применения шаблонов; +- Если под маску подпадает каталог, то все содержимое этого каталога считается подпадающей под маску рекурсивно; +- У получившегося списка файлов werf подсчитывает контрольные суммы с учетом аттрибутов файлов и их содержимого. + +Контрольные суммы подсчитываются вначале сборочного процесса перед запуском какой-либо стадии. + +Пример: + +```yaml +image: app +git: +- add: /src + to: /app + stageDependencies: + beforeSetup: + - "*" +shell: + install: + - echo "install stage" + beforeSetup: + - echo "beforeSetup stage" + setup: + - echo "setup stage" +``` + +В приведенном файле конфигурации `werf.yaml` указан _git mapping_, согласно которому содержимое папки `/src` локального Git-репозитория копируется в папку `/app` собираемого образа. +Во время первой сборки файлы кэшируются в стадии _gitArchive_ и выполняются сборочные инструкции стадий _install_, _beforeSetup_ и _setup_. + +Сборка следующего коммита, в котором будут только изменения файлов за пределами папки `/src`, не приведет к выполнению инструкций каких-либо стадий. Если коммит будет содержать изменение внутри папки `/src`, контрольные суммы файлов подпадающих под маску изменятся, werf применит Git-патч и пересоберёт все пользовательские стадии, начиная со стадии _beforeSetup_, а именно — _beforeSetup_ и _setup_. Применение Git-патча будет выполнено один раз на стадии _beforeSetup_. + +## Зависимость от значения CacheVersion + +Существуют ситуации, когда необходимо принудительно пересобрать все или какую-то конкретную _пользовательскую стадию_. Этого можно достичь, изменяя параметры `cacheVersion` или `CacheVersion`. + +Дайджест пользовательской стадии _install_ зависит от значения параметра `installCacheVersion`. Чтобы пересобрать пользовательскую стадию _install_ (и все последующие стадии), можно изменить значение параметра `installCacheVersion`. + +> Обратите внимание, что параметры `cacheVersion` и `beforeInstallCacheVersion` имеют одинаковый эффект — при изменении этих параметров возникает пересборка стадии _beforeInstall_ и всех последующих стадий. + +### Пример: Общий образ для нескольких приложений + +Вы можете определить образ, содержащий общие системные пакеты в отдельном файле `werf.yaml`. Изменение параметра `cacheVersion` может быть использовано для пересборки этого образа, чтобы обновить версии системных пакетов. + +```yaml +image: ~ +from: ubuntu:latest +shell: + beforeInstallCacheVersion: 2 + beforeInstall: + - apt update + - apt install ... +``` + +Этот образ может быть использован как базовый образ для нескольких приложений (например, если образ с hub.docker.com не удовлетворяет вашим требованиям). + +### Пример использования внешних зависимостей + +Параметры _CacheVersion_ можно использовать совместно [с шаблонами Go-шаблонизатора]({{ "reference/werf_yaml_template_engine.html" | true_relative_url }}), чтобы определить зависимость _пользовательской стадии_ от файлов, не находящихся в Git-репозитории. + +{% raw %} +```yaml +image: ~ +from: ubuntu:latest +shell: + installCacheVersion: {{.Files.Get "some-library-latest.tar.gz" | sha256sum}} + install: + - tar zxf some-library-latest.tar.gz + - +``` +{% endraw %} + +Если использовать, например, скрипт загрузки файла `some-library-latest.tar.gz` и запускать werf для сборки уже после скачивания файла, то пересборка пользовательской стадии _install_ (и всех последующих) будет происходить в случае, если скачан новый (измененный) файл. diff --git a/docs/pages_ru/usage/build_draft/stapel/mounts.md b/docs/pages_ru/usage/build_draft/stapel/mounts.md new file mode 100644 index 0000000000..8b6bc891fa --- /dev/null +++ b/docs/pages_ru/usage/build_draft/stapel/mounts.md @@ -0,0 +1,33 @@ +--- +title: Ускорение сборки и уменьшение размера за счёт монтирования +permalink: usage/build_draft/stapel/mounts.html +author: Artem Kladov , Alexey Igrychev +directive_summary: mount +--- + +Довольно часто бывают случаи, когда при сборке у вас появляются файлы которые нет необходимости оставлять в образе, и их нужно исключить. Например: +- Большинство пакетных менеджеров создают в системе кэш пакетов и служебных файлов. + - [APT](https://wiki.debian.org/Apt) хранит список пакетов в директории `/var/lib/apt/lists/`. + - APT также хранит сами пакеты в директории `/var/cache/apt/`. + - [YUM](http://yum.baseurl.org/) может оставлять скачанные пакеты в директории `/var/cache/yum/.../packages/`. +- Менеджеры пакетов, используемые разработчиками, такие как, ​npm (Node.js), Glide (Go), pip (Python), хранят файлы в директории кэша. +- Компиляция приложений на C/C++ и т.п. языках оставляет объектные файлы и т.п. + +Такого рода файлы: +- не нужны в образе; +- могут значительно увеличивать размер образа; +- могут быть полезны при повторной сборке образа или других образов. + +Если монтировать такого рода файлы в сборочный контейнер, то можно добиться не только уменьшения размера образа, но и ускорения процесса сборки. В Docker такой механизм реализуется с помощью [томов](https://docs.docker.com/storage/volumes/). + +Для указания тома используется директива `mount`. Директории узла сборки монтируются в сборочный контейнер согласно директивам `from`/`fromPath` и `to` описания томов. Для указания в качестве точки монтирования на сборочном узле любого файла или директории, вы можете использовать директиву `fromPath`. Либо, используя директиву `from`, вы можете указать одну из следующих служебных директорий: +- `tmp_dir` временная директория, индивидуальная для каждого описанного образа, создаваемая заново при каждой сборке; +- `build_dir` общая директория, доступная всем образам проекта и сохраняемая между сборками (находится по пути `~/.werf/shared_context/mounts/projects///`). Вы можете использовать эту директорию для хранения, например, кэша и т.п. + +> werf монтирует служебные директории с возможностью чтения и записи при каждой сборке, но в образе содержимого этих директорий не будет. Если вам необходимо сохранить какие-либо данные из этих директорий непосредственно в образе, то вы должны их скопировать при сборке + +На стадии `from`, werf добавляет специальные лейблы к образу стадии, согласно описанных точек монтирования. Затем, на каждой стадии, werf использует эти лейблы при монтировании директорий в сборочный контейнер. Такая реализация позволяет наследовать точки монтирования от [базового образа]({{ "usage/build_draft/stapel/base.html" | true_relative_url }}). + +Также, нужно иметь в виду, что на стадии `from` werf очищает точки монтирования в [базовом образе]({{ "usage/build_draft/stapel/base.html" | true_relative_url }}) (т.е. эти папки будут пусты). + +> По умолчанию, использование директивы `fromPath` и `from: build_dir` запрещено гитерминизмом (подробнее об этом в [статье]({{ "/usage/project_configuration/giterminism.html#mount" | true_relative_url }})) diff --git a/docs/pages_ru/usage/build_draft/stapel/overview.md b/docs/pages_ru/usage/build_draft/stapel/overview.md new file mode 100644 index 0000000000..285f6a37e4 --- /dev/null +++ b/docs/pages_ru/usage/build_draft/stapel/overview.md @@ -0,0 +1,6 @@ +--- +title: Обзор +permalink: usage/build_draft/stapel/overview.html +--- + + diff --git a/docs/pages_ru/usage/build_draft/stapel_all_in_one.md b/docs/pages_ru/usage/build_draft/stapel_all_in_one.md new file mode 100644 index 0000000000..8ac8dede71 --- /dev/null +++ b/docs/pages_ru/usage/build_draft/stapel_all_in_one.md @@ -0,0 +1,1059 @@ +