4 changed files with 289 additions and 0 deletions
@ -0,0 +1,164 @@ |
|||
--- |
|||
title: "Airgap OpenShift Installation: move the registry created using oc adm release mirror between environments" |
|||
date: 2019-11-18T00:00:00+02:00 |
|||
opensource: |
|||
- Ansible |
|||
- OpenShift |
|||
- Skopeo |
|||
--- |
|||
|
|||
Some customers, especially large banks, have very tight security requirements. |
|||
Most of them enforce a complete disconnection of their internal networks from the Internet. |
|||
|
|||
When installing OpenShift in such environments (this is named "disconnected" or ["airgap" installation](http://www.cloud-computing-koeln.de/openshift-4-2-disconnected-install/)), all the OpenShift images have to be fetched (thanks to [oc adm release mirror](https://docs.openshift.com/container-platform/4.2/installing/installing_restricted_networks/installing-restricted-networks-preparations.html)) in a dedicated registry from a bastion host that is both on the internal network and on the Internet. |
|||
|
|||
However, for some customers this is not secure enough. Most of them would rather download all the images locally (using *oc adm release mirror* ?), transport them on a removable media to the internal network and provision the target registry. |
|||
|
|||
As described in this article, [skopeo](https://github.com/nmasse-itix/OpenShift-Examples/blob/master/Using-Skopeo/README.md) and Ansible can be a nice complement of *oc adm release mirror* to achieve this setup. Let's discover how! |
|||
|
|||
The rest of this guide assumes that you followed [the official documentation](https://docs.openshift.com/container-platform/4.2/installing/installing_restricted_networks/installing-restricted-networks-preparations.html) and fetched on the bastion node all the required images in a dedicated registry using *oc adm release mirror*. |
|||
|
|||
First, you will need a token that has administrative privileges on this registry. |
|||
|
|||
```raw |
|||
$ oc whoami -t |
|||
AZERTYUIOPQSDFGHJKLMWXCVBN1234567890azertyu |
|||
``` |
|||
|
|||
Store it somewhere for later use. |
|||
|
|||
```sh |
|||
export TOKEN=$(oc whoami -t) |
|||
``` |
|||
|
|||
Confirm your token can fetch the registry catalog. |
|||
|
|||
```raw |
|||
$ curl -s https://docker-registry.default.svc:5000/v2/_catalog -H "Authorization: Bearer $TOKEN" |jq . |
|||
|
|||
{ |
|||
"repositories": [ |
|||
"openshift/httpd", |
|||
"openshift/java", |
|||
"openshift/jenkins", |
|||
[...] |
|||
"openshift/php", |
|||
"openshift/python" |
|||
] |
|||
} |
|||
``` |
|||
|
|||
As you may have guessed, this is the list of all the images provisioned by the *oc adm release mirror* command that we will need to export using skopeo. |
|||
But before doing so, we need to get the list of all the tags of each image. |
|||
|
|||
Hopefully, there is also an API for this. |
|||
|
|||
```raw |
|||
$ curl -s https://docker-registry.default.svc:5000/v2/openshift/php/tags/list -H "Authorization: Bearer $TOKEN" |jq . |
|||
|
|||
{ |
|||
"name": "openshift/php", |
|||
"tags": [ |
|||
"latest", |
|||
"5.5", |
|||
"5.6", |
|||
"7.0", |
|||
"7.1" |
|||
] |
|||
} |
|||
``` |
|||
|
|||
As an example, to export *openshift/php:5.5* from the *docker-registry.default.svc:5000* registry to the local filesystem (in */tmp/oci_registry*), you could use: |
|||
|
|||
```sh |
|||
skopeo --insecure-policy copy --src-tls-verify=false --src-creds=admin:$TOKEN docker://docker-registry-default.app.itix.fr/openshift/php:5.5 oci:/tmp/oci_registry:openshift/php:5.5 |
|||
``` |
|||
|
|||
We now have the basis to build the Ansible playbook that will dump the registry created by *oc adm release mirror* to the filesystem. |
|||
|
|||
The first step of this playbook would be to fetch the registry catalog. |
|||
There is nothing fancy here, just a plain application of the [uri module](https://docs.ansible.com/ansible/latest/modules/uri_module.html). |
|||
|
|||
```yaml |
|||
- hosts: localhost |
|||
gather_facts: no |
|||
vars: |
|||
registry: docker-registry.default.svc:5000 |
|||
validate_certs: false |
|||
tasks: |
|||
- name: Fetch the catalog of the docker registry |
|||
uri: |
|||
url: 'https://{{ registry }}/v2/_catalog' |
|||
headers: |
|||
Authorization: Bearer {{ token }} |
|||
status_code: 200 |
|||
return_content: yes |
|||
validate_certs: '{{ validate_certs }}' |
|||
register: catalog |
|||
``` |
|||
|
|||
The next step is to iterate on this catalog to fetch the tags of each image. |
|||
The [url lookup](https://docs.ansible.com/ansible/latest/plugins/lookup/url.html) plugin is used to query the registry API. |
|||
The image name is added in front of each tag (to construct the full image name: `image:tag`) using the [cartesian product filter](https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#product-filters). |
|||
|
|||
```yaml |
|||
- name: Construct a list of all available images |
|||
set_fact: |
|||
images: > |
|||
{{ images|default([]) + new_images }} |
|||
vars: |
|||
image_tags: > |
|||
{{ (lookup("url", "https://"~registry~"/v2/"~item~"/tags/list", |
|||
headers={"Authorization": "Bearer "~token}, |
|||
validate_certs=validate_certs)|from_json).tags }} |
|||
new_images: > |
|||
{{ [item] | product(image_tags) | map('join', ':') | list }} |
|||
loop: '{{ catalog.json.repositories }}' |
|||
|
|||
- debug: |
|||
var: images |
|||
``` |
|||
|
|||
When using the url lookup plugin on MacOS, you might need to set the *OBJC_DISABLE_INITIALIZE_FORK_SAFETY* environment variable as explained in [#32499](https://github.com/ansible/ansible/issues/32499). |
|||
|
|||
```sh |
|||
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES |
|||
``` |
|||
|
|||
Finally, skopeo is called to download each image to */tmp/oci_registry*. |
|||
|
|||
```yaml |
|||
- hosts: localhost |
|||
gather_facts: no |
|||
vars: |
|||
target: /tmp/oci_registry |
|||
[...] |
|||
|
|||
tasks: |
|||
|
|||
[...] |
|||
|
|||
- name: Downloading OpenShift images... |
|||
command: skopeo --insecure-policy copy --src-tls-verify={{ validate_certs|bool|ternary('true','false') }} --src-creds=admin:{{ token }} 'docker://{{ registry }}/{{ item }}' 'oci:{{ target }}:{{ item }}' |
|||
with_items: '{{ images }}' |
|||
``` |
|||
|
|||
The complete playbook [is available here](pull.yaml) and can be run as follow. |
|||
|
|||
```sh |
|||
ansible-playbook pull.yaml -e token=$TOKEN |
|||
``` |
|||
|
|||
This will dump the registry at *docker-registry.default.svc:5000* to */tmp/oci_registry*. |
|||
If you want to target another registry or store the images somewhere else, you can pass the *registry* or *target* extra variables. |
|||
|
|||
```sh |
|||
ansible-playbook pull.yaml -e token=$TOKEN -e registry=docker-registry-default.app.openshift.test -e target=/tmp/oci_registry |
|||
``` |
|||
|
|||
The images are stored as an OCI registry whose format is standardized. |
|||
It can be moved somewhere else, using a removable media for instance. |
|||
|
|||
How this OCI registry can be imported in the target registry is a subject for another article. |
|||
|
|||
Stay tuned. |
|||
@ -0,0 +1,42 @@ |
|||
--- |
|||
title: "Ansible: Add a prefix or suffix to all items of a list" |
|||
date: 2019-11-18T00:00:00+02:00 |
|||
opensource: |
|||
- Ansible |
|||
--- |
|||
|
|||
Recently, in [one of my Ansible playbooks](../airgap-openshift-installation-move-registry-created-using-oc-adm-release-mirror-between-environments) I had to prefix all items of a list with a chosen string. |
|||
|
|||
Namely, from the following list: |
|||
|
|||
```python |
|||
[ "bar", "bat", "baz" ] |
|||
``` |
|||
|
|||
I want to have: |
|||
|
|||
```python |
|||
[ "foobar", "foobat", "foobaz" ] |
|||
``` |
|||
|
|||
The recipe I used to add a prefix to all items of a list is: |
|||
|
|||
```yaml |
|||
- debug: |
|||
var: result |
|||
vars: |
|||
prefix: foo |
|||
a_list: [ "bar", "bat", "baz" ] |
|||
result: "{{ [prefix] | product(a_list) | map('join') | list }}" |
|||
``` |
|||
|
|||
If you need to add a suffix to all items of a list instead, you can use: |
|||
|
|||
```yaml |
|||
- debug: |
|||
var: result |
|||
vars: |
|||
suffix: foo |
|||
a_list: [ "bar", "bat", "baz" ] |
|||
result: "{{ a_list | product([suffix]) | map('join') | list }}" |
|||
``` |
|||
@ -0,0 +1,20 @@ |
|||
--- |
|||
title: "Check the Ansible version number in a playbook" |
|||
date: 2019-11-18T00:00:00+02:00 |
|||
opensource: |
|||
- Ansible |
|||
--- |
|||
|
|||
My Ansible playbooks sometimes use features that are available only in a very recent versions of Ansible. |
|||
|
|||
To prevent unecessary troubles to the team mates that will execute them, I like to add a task at the very beginning of my playbooks to check the Ansible version number and abort if the requirements are not met. |
|||
|
|||
```yaml |
|||
- name: Verify that Ansible version is >= 2.4.6 |
|||
assert: |
|||
that: "ansible_version.full is version_compare('2.4.6', '>=')" |
|||
msg: >- |
|||
This module requires at least Ansible 2.4.6. The version that comes |
|||
with RHEL and CentOS by default (2.4.2) has a known bug that prevent |
|||
this role from running properly. |
|||
``` |
|||
@ -0,0 +1,63 @@ |
|||
--- |
|||
- name: Pull a complete Docker registry |
|||
hosts: localhost |
|||
become: no |
|||
gather_facts: no |
|||
vars: |
|||
registry: docker-registry.default.svc:5000 |
|||
validate_certs: false |
|||
target: /tmp/oci_registry |
|||
tasks: |
|||
|
|||
- name: Verify that Ansible version is >= 2.9 |
|||
assert: |
|||
that: "ansible_version.full is version_compare('2.9.0', '>=')" |
|||
msg: >- |
|||
This playbook uses the 'headers' property of the 'url' filter and thus |
|||
requires Ansible 2.9 |
|||
|
|||
- assert: |
|||
that: |
|||
- token is defined |
|||
msg: > |
|||
Please pass an administrative token to connect to '{{ registry }}' |
|||
as an extra var (-e token=bla.bla.bla). |
|||
|
|||
- name: Fetch the catalog of the docker registry |
|||
uri: |
|||
url: 'https://{{ registry }}/v2/_catalog' |
|||
headers: |
|||
Authorization: Bearer {{ token }} |
|||
status_code: 200 |
|||
return_content: yes |
|||
validate_certs: '{{ validate_certs }}' |
|||
register: catalog |
|||
|
|||
- name: Construct a list of all available images |
|||
set_fact: |
|||
images: > |
|||
{{ images|default([]) + new_images }} |
|||
vars: |
|||
image_tags: > |
|||
{{ (lookup("url", "https://" ~ registry ~ "/v2/" ~ item ~ "/tags/list", |
|||
headers={"Authorization": "Bearer " ~ token}, |
|||
validate_certs=validate_certs)|from_json).tags }} |
|||
new_images: > |
|||
{{ [item] | product(image_tags) | map('join', ':') | list }} |
|||
loop: '{{ catalog.json.repositories }}' |
|||
|
|||
- debug: |
|||
var: images |
|||
|
|||
- pause: |
|||
prompt: "Would pull {{ images|length }} images from {{ registry }}. Continue?" |
|||
|
|||
- name: Create a directory to hold the images |
|||
file: |
|||
path: '{{ target }}' |
|||
state: directory |
|||
register: mkdir |
|||
|
|||
- name: Downloading OpenShift images... |
|||
command: skopeo --insecure-policy copy --src-tls-verify={{ validate_certs|bool|ternary('true','false') }} --src-creds=admin:{{ token }} 'docker://{{ registry }}/{{ item }}' 'oci:{{ target }}:{{ item }}' |
|||
with_items: '{{ images }}' |
|||
Loading…
Reference in new issue