GitOps is a great solution for continuous delivery of Kubernetes applications, as it’s based on Git, a tool that many if not all developers are familiar with. Developers can manage deployed applications by storing their desired state in Git and reap the benefits, such as revision history, code reviews, and branching for their deployments.
GitOps tools such as ArgoCD and Red Hat Advanced Cluster Management (RHACM) have been focused on Kubernetes environments for the most part, but what about lighter weight environments where Kubernetes isn’t required?
Welcome to FetchIt! It’s a lightweight, simple, hands-off GitOps tool for deploying and managing containers through Podman. FetchIt supports various deployment methods, such as systemd, Ansible, and podman-kube. It consumes the same files you would use to deploy heavier weight Kubernetes clusters. In this post, I explain how to run FetchIt and how FetchIt works. I also describe the methods of deployment that FetchIt supports.
Note: Red Hat’s Emerging Technologies blog includes posts that discuss technologies that are under active development in upstream open source communities and at Red Hat. We believe in sharing early and often the things we’re working on, but we want to note that unless otherwise stated the technologies and how-tos shared here aren’t part of supported products, nor promised to be in the future.
FetchIt has a few requirements: a config file, Podman, and having the appropriate Podman socket running. Podman has a user socket and a root socket. To enable and run the user socket, use this command:
systemctl --user enable podman.socket --now
For the root user, use this command:
sudo systemctl enable podman.socket --now
How does FetchIt work?
FetchIt works by monitoring the remote Git repositories specified in the configuration file and polling them every so often to check for changes. If a change is detected, based on the methods assigned to the target repository, FetchIt will perform the required update to the deployment on your server(s). This is achieved by communicating with the Podman socket to create containers or pods, or by writing a systemd service file to your server(s) then running it.
There are two recommended ways of running FetchIt: as a systemd service or directly within a container. In either case, it will be running within a container.
Running FetchIt as a systemd service
You can run FetchIt as root, using the Podman root socket, or as a user using Podman’s user socket. Ensure the socket you intend to use is enabled. The systemd service runs the FetchIt container for the user, and all a user has to do is make sure they have written a config at
Running FetchIt in a container
The other way, running it directly within a container, requires running that container with some options.
The command can be found below:
podman run -d --rm --name fetchit \ -v fetchit-volume:/opt \ -v $HOME/.fetchit:/opt/mount \ -v /run/user/$(id -u)/podman/podman.sock:/run/podman/podman.sock \ quay.io/fetchit/fetchit-amd:latest
Note: if running with SELinux in enforcing mode, add the podman flag
--security-opt label=disable to the run command . This turns off label separation in the fetchit pod.
The important arguments in this command are the volume mounts; the rest are optional. This command mounts a volume with the same name specified in the config at
/opt, which is where FetchIt clones the Git repositories it’s monitoring. This command also mounts the user’s config.yaml at
/opt/mount. The final mount is mounting the host’s podman socket to the corresponding socket in the container. That’s it—you now have an instance of FetchIt running on your machine.
The YAML configuration file is the interface for users to configure FetchIt. It contains a list of target repositories, the options for the repository, and which methods to run for each repository. There is also a field for specifying the name of the Podman volume being mounted at
/opt. There is a directory full of examples on the FetchIt GitHub, so let’s take a look at the full-suite.yaml, a config file used to test all methods in our CI:
targetConfigs: - url: http://github.com/containers/fetchit raw: - name: raw-ex targetPath: examples/raw schedule: "*/1 * * * *" pullImage: false systemd: - name: sysd-ex targetPath: examples/systemd root: true enable: false schedule: "*/1 * * * *" ansible: - name: ans-ex targetPath: examples/ansible sshDirectory: /root/.ssh schedule: "*/1 * * * *" filetransfer: - name: ft-ex targetPath: examples/filetransfer destinationDirectory: /tmp/ft schedule: "*/1 * * * *" branch: main prune: All: true Volumes: false schedule: "*/1 * * * *"
You start by configuring a list of targets. Each target has a URL field and their methods fields. The URL points to the remote repository you want to target. The methods contain fields defining each method you want to run on this repository, and their options. Each method has a name field, a target path field, and a schedule field. The target path field defines the directory containing the files describing what that method should deploy. The schedule field describes when the method should run, in cron format. Other fields on methods are usually specific to that method. I explain individual methods below.
FetchIt attempts to run every method it can and will run other methods even if one fails. The goal is to have FetchIt keep going even if it hits some errors and notify the user that these errors are happening so the user can identify the cause. This is the level of reliability designed into FetchIt.
Here’s a recap before continuing:
- FetchIt requires a user to have a config file, Podman, the appropriate Podman socket running, and SELinux in permissive mode.
- To run FetchIt, you have two options: as a systemd service using one of the two service files from this repository, or directly in a container, as described above.
- FetchIt’s configuration is organized into targets and methods to be run for each target.
- Each method requires a schedule to specify at what intervals it should run.
- Many methods require the targetPath field to specify where to find the files to be deployed using that method.
You can see that FetchIt is organized into targets that represent git repositories. Users can specify multiple targets, with each target running up to one of each unique method described below. Schedules are scoped to their respective methods, and the schedules of different methods on the same target are completely independent. A target can not have two instances of the same method running. If the method or methods copy files to the same directory on the machine, or create containers with the same name, there will be conflicts that must be resolved by the user.
The raw method is the simplest. It lets users create containers from a small spec. Here is an example of a raw method configuration:
raw: - name: raw-ex targetPath: examples/raw schedule: "*/1 * * * *" pullImage: false
The only unique option for this method is the pullImage option, which forces a pull of the image specified in the spec below on every run of the method. In the
examples/raw directory specified in the targetPath option, you can find YAML files with the following format:
Image: "docker.io/mmumshad/simple-webapp-color:latest" Name: "colors2" Env: APP_COLOR: "pink" tree: "trunk" Ports: - container_port: 8080 host_port: 9080 range: 0
This YAML specifies a simple container configuration with a container name, image, environment variables and a list of port mappings. On every run of the raw method, FetchIt will parse the files in the target directory, check whether those files’ images are on the machine, and pull them if they aren’t (or if the pullmage option is set to true). Then it will remove previous containers with the same name as the one specified in the file and create new containers according to the spec supplied in the files.
The systemd method lets users specify systemd service files to deposit on the local machine in the proper systemd directory. There are many options for the systemd method configuration. The first is a simple one:
systemd: - name: sysd-ex targetPath: examples/systemd root: true enable: false schedule: "*/1 * * * *"
As you can see in the example above, there are three unique options for the systemd method. The first is the root option, which lets the user decide whether they want the service file to be deposited in the root systemd directory to run as a root service, or placed in the non-root systemd directory to run as a non-root service. At the moment, this must be set to true if a user wants FetchIt to enable the service automatically. If the root option is not set, users will have to enable the service themselves using a command such as
systemctl –user enable.
The second option is
enable, which, if set, enables the service for the user automatically after depositing the file in the appropriate location on the machine. The final option is
restart, which restarts the service (after applying the changed service file) when systemd detects a change.
The filetransfer method allows a user to deposit files from a remote repository into a specific location on a local machine. This method is used within the systemd method to get the service files into the right location. Here is an example configuration:
filetransfer: - name: ft-ex targetPath: examples/filetransfer destinationDirectory: /tmp/ft schedule: "*/1 * * * *"
For this configuration the unique option is the
destinationDirectory option. This option names the directory to deposit the file.
The Ansible method allows a user to run the Ansible playbooks found at the target path, within a container that FetchIt deploys to modify the host running FetchIt. Here is an example configuration for the Ansible method:
ansible: - name: ans-ex targetPath: examples/ansible sshDirectory: /root/.ssh schedule: "*/1 * * * *"
The unique option here is
sshDirectory. This option lets FetchIt know which directory to mount the required .ssh directory containing the SSH keys and
The kube method uses the
podman-play-kube functionality to allow users to deploy a Kubernetes YAML specification defining config maps, PVCs, pods, and deployments to run via Podman. PVCs map to Podman volumes, and config maps are loaded into memory when a kube spec is applied. Here is an example kube method configuration:
kube: - name: kube-ex targetPath: examples/kube schedule: "*/1 * * * *"
There is no unique option for this method, but within the
targetPath directory you will find Kubernetes specification files such as this one:
apiVersion: v1 kind: ConfigMap metadata: name: env data: APP_COLOR: blue tree: trunk --- apiVersion: v1 kind: Pod metadata: name: colors_pod spec: containers: - name: colors-kubeplay image: docker.io/mmumshad/simple-webapp-color:latest ports: - containerPort: 8080 hostPort: 7080 envFrom: - configMapRef: name: env optional: false
FetchIt will run
podman play kube on this file, and Podman will create a pod that has the container declared here with the port mapping declared in the file, consuming the config map declared at the beginning of the file. You can learn more about
podman kube play in the Podman documentation.
The prune option is a special method that does not use any files and thus does not need a target, but serves as a way to remove unused containers and images. It runs
podman system prune with the options set in the method configuration, according to the schedule provided in the configuration:
prune: All: true Volumes: false schedule: "*/1 * * * *"
The All and Volumes options in the configuration map to the
podman system prune flags that share the same name. Learn more about
podman system prune in the documentation.
Finally, the configuration reload method is also a special method, like the clean and systemd auto-update. This method is a little complicated and deserves its own article, but I mention it here for completeness.
It’s important to mention again that all of these methods are used in a GitOps fashion. There are remote target repositories that FetchIt knows about from the configuration file it is watching for changes. For each change, FetchIt fetches the update to that file and runs what has to be run to update the state of the running containers so they match what is declared in the repository. Because of this, FetchIt ensures that each machine will make the smallest amount of changes to its deployments, to match the state declared within the remote repository..
The FetchIt GitHub repo has a very complete examples directory to explore if you’re interested in learning more about how to configure FetchIt. There is also a read the docs website with more information about the project.
If you are interested in contributing to the project, feel free to visit the repository and open issues or pull requests.