Running Ansible GitOps From OneDev Git Forge
In previous posts I have discussed using OneDev as my Git repository, but it is much more capable than that. It also has "Agents" which can run your builds based on triggers and webhooks (among many other features). In this post I will discuss how I am using OneDev, Job Secrets, and Ansible to manage my personal cloud server in a "GitOps Way".
What Is GitOps?
As with most things in tech, the term gets redefined and mutated in the constant and neverending marketing hype. That said, in my mind, the simplest definition of GitOps is this:
GitOps is the practice of storing infrastructure configurations as code in a Git repository such that when a change is merged into the repository, the infrastructure will automatically have those changes applied.
That's it! How you accomplish that is up to you. It could be using ArgoCD on Kubernetes, it could be through Azure DevOps, it could be using GitHub actions, or any other way you might like to do it. The HOW is not as important as the WHAT.
Before You Start, Secrets Need Managing
In general, for GitOps to work, you will need some sort of credentials and/or secrets in order to apply your Infrastructure-as-Code. Those could be passwords, tokens, vault credentials, etc... And you will need to manage those secrets carefully. In this post, I will talk about managing bulk secrets and ansible vault secrets. Ansible provides pretty comprehensive secrets encryption so that you can safely store your bulk secrets alongside your Ansible roles and playbooks.
The Ansible Vault secrets are encrypted using an ansible vault key. The vault key is needed to decrypt the bulk secrets every time that ansible is run, so in OneDev we would store that as a Job Secret which can be referenced in our CD job, but cannot be viewed after the value is set.
Create Your Ansible Repository
I am not going to go into too much detail about the content of the Ansible repository as that's explained very well in the Ansible Docs. What I will say is that you need to create a file with a specific name where you will store your secrets. That file needs to be in your .gitignore so that it NEVER gets stored in Git. And finally, you can create an encrypted version of those secrets using the command below:
# Prompts the user to input the vault key
ansible-vault encrypt --output=secret_enc.yaml --ask-vault-password secrets.yaml
# Reads the vault key from the specified file
ansible-vault encrypt --output=secret_enc.yaml --vault-pass-file pass-ansible.txt secrets.yamlThe encrypted version of the file DOES get stored in your Git repository.
Create The GitOps Job In OneDev
Again, I am not going to go into the specifics of creating jobs in OneDev here, but the documentation can be found at https://docs.onedev.io/
What is more important to this post is HOW we inject the Ansible vault key into our Job.
On your local computer, you might run the ansible playbook like this:
ansible-playbook -i inventory -e @secrets_enc.yaml --ask-vault-pass my-playbook.yamlYou cannot input the vault key on the CLI when running it from the Job engine in OneDev, so we need a different option. Ansible supports passing the key in from a file, so we COULD add a file to the repo, but that would defeat the purposes of encrypting our bulk secrets. Instead, we can use a little shell trick called process substitution. It's an available option in bash and zsh, and in our case we could apply it like this:
ansible-playbook -i inventory -e @secrets_enc.yaml --vault-pass-file <(echo @secret:onedev-ansible-vault-key@) my-playbook.yamlThe @secret:onedev-ansible-vault-key@ refers to a Job Secret in OneDev. The <(<wrapped process>) syntax is very simple. Whatever command you put in the parenthesis will be presented as a "virtual file" which only exists for the duration of the command sequence. This is perfect for our purposes since we do not want our vault key sitting around anywhere unencrypted.
Summary
This is ONE way you could implement GitOps using Ansible, but certainly not the only way. I won't try to say that any particular way is the BEST way, because that very much depends on your environment, your needs, your skill level, etc... I hope you at least found this informative though!
Want to complain about how bad my solution is? Follow me on Mastodon and send me all of the messages telling me how badly I screwed this up and perhaps I can learn a few new tricks along the way! 😛