├── .ansible-lint ├── .github ├── FUNDING.yml └── workflows │ └── stale.yml ├── .travis.yml ├── .yamllint ├── README.md ├── build ├── Dockerfile ├── chain-operator-files.yml └── test-framework │ ├── Dockerfile │ └── ansible-test.sh ├── deploy ├── crds │ ├── drupal_v1alpha1_drupal_cr.yaml │ └── drupal_v1alpha1_drupal_crd.yaml ├── drupal-operator.yaml ├── operator.yaml ├── role.yaml ├── role_binding.yaml └── service_account.yaml ├── main.yml ├── molecule ├── default │ ├── asserts.yml │ ├── converge.yml │ ├── molecule.yml │ └── prepare.yml ├── test-local │ ├── converge.yml │ ├── molecule.yml │ └── prepare.yml └── test-minikube │ ├── converge.yml │ ├── molecule.yml │ └── prepare.yml ├── requirements.yml ├── roles └── drupal │ ├── README.md │ ├── defaults │ └── main.yml │ ├── meta │ └── main.yml │ └── tasks │ ├── database.yml │ └── main.yml └── watches.yaml /.ansible-lint: -------------------------------------------------------------------------------- 1 | skip_list: 2 | - '306' 3 | - '602' 4 | - '503' 5 | 6 | exclude_paths: 7 | - deploy/ 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | --- 3 | github: geerlingguy 4 | patreon: geerlingguy 5 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Close inactive issues 3 | 'on': 4 | schedule: 5 | - cron: "55 23 * * 4" # semi-random time 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v8 15 | with: 16 | days-before-stale: 120 17 | days-before-close: 60 18 | exempt-issue-labels: bug,pinned,security,planned 19 | exempt-pr-labels: bug,pinned,security,planned 20 | stale-issue-label: "stale" 21 | stale-pr-label: "stale" 22 | stale-issue-message: | 23 | This issue has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 24 | 25 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 26 | close-issue-message: | 27 | This issue has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 28 | stale-pr-message: | 29 | This pr has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 30 | 31 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 32 | close-pr-message: | 33 | This pr has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 34 | repo-token: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: required 3 | services: docker 4 | language: python 5 | 6 | before_install: 7 | # Upgrade Docker to work with docker-py. 8 | - curl https://gist.githubusercontent.com/geerlingguy/ce883ad4aec6a5f1187ef93bd338511e/raw/36612d28981d92863f839c5aefe5b7dd7193d6c6/travis-ci-docker-upgrade.sh | sudo bash 9 | 10 | install: 11 | - pip install ansible molecule[docker] yamllint ansible-lint docker openshift jmespath 12 | 13 | script: 14 | - molecule test -s test-local 15 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | braces: 6 | max-spaces-inside: 1 7 | level: error 8 | brackets: 9 | max-spaces-inside: 1 10 | level: error 11 | line-length: disable 12 | truthy: disable 13 | document-start: 14 | ignore: | 15 | .github/stale.yml 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drupal Operator for Kubernetes 2 | 3 | [![Build Status](https://travis-ci.com/geerlingguy/drupal-operator.svg?branch=master)](https://travis-ci.com/geerlingguy/drupal-operator) 4 | 5 | This is a Drupal Operator, which makes management of Drupal instances running inside Kuberenetes clusters easy. It was built with the [Operator SDK](https://github.com/operator-framework/operator-sdk) using [Ansible Operator](https://www.ansible.com/blog/ansible-operator). 6 | 7 | ## Usage 8 | 9 | This Kubernetes Operator is meant to be deployed in your Kubernetes cluster(s) and can manage one or more Drupal instances in any namespace. 10 | 11 | First you need to deploy Drupal Operator into your cluster: 12 | 13 | kubectl apply -f https://raw.githubusercontent.com/geerlingguy/drupal-operator/master/deploy/drupal-operator.yaml 14 | 15 | Then you can create instances of Drupal in any namespace, for example: 16 | 17 | 1. Create a file named `my-drupal-site.yml` with the following contents: 18 | 19 | ``` 20 | --- 21 | apiVersion: drupal.drupal.org/v1alpha1 22 | kind: Drupal 23 | metadata: 24 | name: my-drupal-site 25 | namespace: default 26 | spec: 27 | # The container image to use for the Drupal deployment. 28 | drupal_image: 'drupal:8.8-apache' 29 | 30 | # Set this to 'true' to use a single-pod database managed by this operator. 31 | manage_database: true 32 | database_image: mariadb:10 33 | database_pvc_size: 1Gi 34 | database_password: change-me 35 | 36 | # Set this to 'true' to have this operator manage Ingress for the site. 37 | manage_ingress: true 38 | drupal_hostname: mysite.com 39 | ``` 40 | 41 | 2. Use `kubectl` to create the Drupal site in your cluster: 42 | 43 | ``` 44 | kubectl apply -f my-drupal-site.yml 45 | ``` 46 | 47 | There are many other options you can provide in the `spec`, to control the deployment and how it integrates with external services (e.g. set `manage_database` to `false` and override the `database_` options to integrate with a separate database backend). 48 | 49 | > You can also deploy `Drupal` applications into other namespaces by changing `metadata.namespace`, or deploy multiple `Drupal` instances into the same namespace by changing `metadata.name`. 50 | 51 | ## Development 52 | 53 | ### Release Process 54 | 55 | There are a few moving parts to this project: 56 | 57 | 1. The Docker image which powers Drupal Operator. 58 | 2. The `drupal-operator.yaml` Kubernetes manifest file which initially deploys the Operator into a cluster. 59 | 60 | Each of these must be appropriately built in preparation for a new tag: 61 | 62 | #### Build a new release of the Operator for Docker Hub 63 | 64 | Run the following command inside this directory: 65 | 66 | operator-sdk build geerlingguy/drupal-operator:0.1.1 67 | 68 | Then push the generated image to Docker Hub: 69 | 70 | docker push geerlingguy/drupal-operator:0.1.1 71 | 72 | #### Build a new version of the `drupal-operator.yaml` file 73 | 74 | Verify the `build/chain-operator-files.yml` playbook has the most recent version/tag of the Docker image, then run the playbook in the `build/` directory: 75 | 76 | ansible-playbook chain-operator-files.yml 77 | 78 | After it is built, test it on a local cluster: 79 | 80 | minikube start 81 | minikube addons enable ingress 82 | kubectl apply -f deploy/drupal-operator.yaml 83 | kubectl create namespace example-drupal 84 | kubectl apply -f deploy/crds/drupal_v1alpha1_drupal_cr.yaml 85 | 86 | minikube delete 87 | 88 | If everything is deployed correctly, commit the updated version and push it up to GitHub, tagging a new repository release with the same tag as the Docker image. 89 | 90 | ### Testing 91 | 92 | #### Testing in Docker (standalone) 93 | 94 | molecule test -s test-local 95 | 96 | This environment is meant for headless testing (e.g. in a CI environment, or when making smaller changes which don't need to be verified through a web interface). It is difficult to test things like Drupal's front-end or connecting other applications on your local machine to services running inside the cluster, since it is inside a Docker container with no static IP address. 97 | 98 | #### Testing in Minikube 99 | 100 | minikube start 101 | minikube addons enable ingress 102 | molecule test -s test-minikube 103 | 104 | [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) is a more full-featured test environment running inside a full VM on your computer, with an assigned IP address. This makes it easier to test things like NodePort services and Ingress from outside the Kubernetes cluster (e.g. in a browser on your computer). 105 | 106 | Once the operator is deployed, you can visit the Drupal in your browser by following these steps: 107 | 108 | 1. Make sure you have an entry like `IP_ADDRESS example-drupal.test` in your `/etc/hosts` file. (Get the IP address with `minikube ip`.) 109 | 2. Visit `http://example-drupal.test/` in your browser. 110 | 111 | ## Authors 112 | 113 | This is a fork of the original [drupal-operator](https://github.com/thom8/drupal-operator/) by [Thom Toogood](https://github.com/thom8). We have long collaborated on Drupal DevOps-related projects and Thom's work is impeccable. 114 | 115 | This fork is maintained by [Jeff Geerling](https://www.jeffgeerling.com), author of [Ansible for DevOps](https://www.ansiblefordevops.com) and [Ansible for Kubernetes](https://www.ansibleforkubernetes.com). 116 | -------------------------------------------------------------------------------- /build/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/operator-framework/ansible-operator:v0.16.0 2 | 3 | # Install Ansible requirements. 4 | COPY requirements.yml ${HOME}/requirements.yml 5 | RUN ansible-galaxy collection install -r ${HOME}/requirements.yml \ 6 | && chmod -R ug+rwx ${HOME}/.ansible 7 | 8 | COPY watches.yaml ${HOME}/watches.yaml 9 | 10 | COPY main.yml ${HOME}/main.yml 11 | COPY roles/ ${HOME}/roles/ 12 | -------------------------------------------------------------------------------- /build/chain-operator-files.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # To run: `ansible-playbook chain-operator-files.yml` 3 | - name: Chain operator files together for easy deployment. 4 | hosts: localhost 5 | connection: local 6 | gather_facts: false 7 | 8 | vars: 9 | operator_image: geerlingguy/drupal-operator:0.1.1 10 | pull_policy: Always 11 | operator_file_path: "../deploy/drupal-operator.yaml" 12 | operator_template: "../deploy/operator.yaml" 13 | 14 | tasks: 15 | - name: Clear out current contents of drupal-operator.yml 16 | copy: 17 | dest: "{{ operator_file_path }}" 18 | content: '' 19 | force: true 20 | 21 | - name: Concatenate operator files into drupal-operator.yml 22 | blockinfile: 23 | path: "{{ operator_file_path }}" 24 | block: "{{ item }}" 25 | marker: "" 26 | marker_begin: "" 27 | marker_end: "" 28 | insertafter: "EOF" 29 | with_file: 30 | - "../deploy/crds/drupal_v1alpha1_drupal_crd.yaml" 31 | - "../deploy/role.yaml" 32 | - "../deploy/role_binding.yaml" 33 | - "../deploy/service_account.yaml" 34 | - "../deploy/operator.yaml" 35 | 36 | - name: Remove space at beginning of drupal-operator.yaml 37 | shell: > 38 | echo "$(tail -n +2 {{ operator_file_path }})" > {{ operator_file_path }} 39 | changed_when: true 40 | 41 | - name: Template the drupal-operator.yaml file into drupal-operator.yaml 42 | template: 43 | src: "{{ operator_file_path }}" 44 | dest: "{{ operator_file_path }}" 45 | -------------------------------------------------------------------------------- /build/test-framework/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASEIMAGE 2 | FROM ${BASEIMAGE} 3 | USER 0 4 | 5 | RUN yum install -y python-devel gcc libffi-devel 6 | RUN pip install molecule==2.20.1 7 | 8 | ARG NAMESPACEDMAN 9 | ADD $NAMESPACEDMAN /namespaced.yaml 10 | ADD build/test-framework/ansible-test.sh /ansible-test.sh 11 | RUN chmod +x /ansible-test.sh 12 | USER 1001 13 | ADD . /opt/ansible/project 14 | -------------------------------------------------------------------------------- /build/test-framework/ansible-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export WATCH_NAMESPACE=${TEST_NAMESPACE} 3 | (/usr/local/bin/entrypoint)& 4 | trap "kill $!" SIGINT SIGTERM EXIT 5 | 6 | cd ${HOME}/project 7 | exec molecule test -s test-cluster 8 | -------------------------------------------------------------------------------- /deploy/crds/drupal_v1alpha1_drupal_cr.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: drupal.drupal.org/v1alpha1 3 | kind: Drupal 4 | metadata: 5 | name: example-drupal 6 | namespace: example-drupal 7 | spec: 8 | # The container image to use for the Drupal deployment. 9 | drupal_image: 'drupal:8.8-apache' 10 | 11 | # You should generate your own hash salt, e.g. `Crypt::randomBytesBase64(55)`. 12 | drupal_hash_salt: 'fe918c992fb1bcfa01f32303c8b21f3d0a0' 13 | 14 | # Set this value if you wish to define a trusted host pattern. 15 | # drupal_trusted_host_patterns: 'provide trusted_host_patterns here' 16 | 17 | # The size of the files directory PVC. 18 | files_pvc_size: 1Gi 19 | 20 | # Database options (will be used regardless of database location). 21 | database_name: drupal 22 | database_username: drupal 23 | database_password: drupal 24 | database_host: 'example-drupal-mariadb' 25 | 26 | # Set this to 'true' to use a single-pod database managed by this operator. 27 | manage_database: true 28 | database_image: mariadb:10 29 | database_pvc_size: 1Gi 30 | 31 | # Set this to 'true' to have this operator manage Ingress for the site. 32 | manage_ingress: true 33 | drupal_hostname: example-drupal.test 34 | -------------------------------------------------------------------------------- /deploy/crds/drupal_v1alpha1_drupal_crd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1beta1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: drupals.drupal.drupal.org 6 | spec: 7 | group: drupal.drupal.org 8 | names: 9 | kind: Drupal 10 | listKind: DrupalList 11 | plural: drupals 12 | singular: drupal 13 | scope: Namespaced 14 | subresources: 15 | status: {} 16 | version: v1alpha1 17 | versions: 18 | - name: v1alpha1 19 | served: true 20 | storage: true 21 | -------------------------------------------------------------------------------- /deploy/drupal-operator.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: drupal-operator 7 | rules: 8 | - apiGroups: 9 | - "" 10 | resources: 11 | - pods 12 | - services 13 | - endpoints 14 | - persistentvolumeclaims 15 | - events 16 | - configmaps 17 | - secrets 18 | verbs: 19 | - '*' 20 | - apiGroups: 21 | - apps 22 | resources: 23 | - deployments 24 | - daemonsets 25 | - replicasets 26 | - statefulsets 27 | verbs: 28 | - '*' 29 | - apiGroups: 30 | - extensions 31 | resources: 32 | - ingresses 33 | verbs: 34 | - '*' 35 | - apiGroups: 36 | - monitoring.coreos.com 37 | resources: 38 | - servicemonitors 39 | verbs: 40 | - get 41 | - create 42 | - apiGroups: 43 | - apps 44 | resourceNames: 45 | - drupal-operator 46 | resources: 47 | - deployments/finalizers 48 | verbs: 49 | - update 50 | - apiGroups: 51 | - "" 52 | resources: 53 | - pods 54 | verbs: 55 | - get 56 | - apiGroups: 57 | - apps 58 | resources: 59 | - replicasets 60 | verbs: 61 | - get 62 | - apiGroups: 63 | - drupal.drupal.org 64 | resources: 65 | - '*' 66 | verbs: 67 | - '*' 68 | 69 | --- 70 | kind: ClusterRoleBinding 71 | apiVersion: rbac.authorization.k8s.io/v1 72 | metadata: 73 | name: drupal-operator 74 | subjects: 75 | - kind: ServiceAccount 76 | name: drupal-operator 77 | namespace: default 78 | roleRef: 79 | kind: ClusterRole 80 | name: drupal-operator 81 | apiGroup: rbac.authorization.k8s.io 82 | 83 | --- 84 | apiVersion: v1 85 | kind: ServiceAccount 86 | metadata: 87 | name: drupal-operator 88 | namespace: default 89 | 90 | --- 91 | apiVersion: apps/v1 92 | kind: Deployment 93 | metadata: 94 | name: drupal-operator 95 | namespace: default 96 | spec: 97 | replicas: 1 98 | selector: 99 | matchLabels: 100 | name: drupal-operator 101 | template: 102 | metadata: 103 | labels: 104 | name: drupal-operator 105 | spec: 106 | serviceAccountName: drupal-operator 107 | containers: 108 | - name: ansible 109 | command: 110 | - /usr/local/bin/ao-logs 111 | - /tmp/ansible-operator/runner 112 | - stdout 113 | image: "geerlingguy/drupal-operator:0.1.1" 114 | imagePullPolicy: "Always" 115 | volumeMounts: 116 | - mountPath: /tmp/ansible-operator/runner 117 | name: runner 118 | readOnly: true 119 | - name: operator 120 | image: "geerlingguy/drupal-operator:0.1.1" 121 | imagePullPolicy: "Always" 122 | volumeMounts: 123 | - mountPath: /tmp/ansible-operator/runner 124 | name: runner 125 | env: 126 | # Watch all namespaces (cluster-scoped). 127 | - name: WATCH_NAMESPACE 128 | value: "" 129 | - name: POD_NAME 130 | valueFrom: 131 | fieldRef: 132 | fieldPath: metadata.name 133 | - name: OPERATOR_NAME 134 | value: "drupal-operator" 135 | volumes: 136 | - name: runner 137 | emptyDir: {} 138 | 139 | --- 140 | apiVersion: apiextensions.k8s.io/v1beta1 141 | kind: CustomResourceDefinition 142 | metadata: 143 | name: drupals.drupal.drupal.org 144 | spec: 145 | group: drupal.drupal.org 146 | names: 147 | kind: Drupal 148 | listKind: DrupalList 149 | plural: drupals 150 | singular: drupal 151 | scope: Namespaced 152 | subresources: 153 | status: {} 154 | version: v1alpha1 155 | versions: 156 | - name: v1alpha1 157 | served: true 158 | storage: true 159 | -------------------------------------------------------------------------------- /deploy/operator.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: drupal-operator 6 | namespace: default 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | name: drupal-operator 12 | template: 13 | metadata: 14 | labels: 15 | name: drupal-operator 16 | spec: 17 | serviceAccountName: drupal-operator 18 | containers: 19 | - name: ansible 20 | command: 21 | - /usr/local/bin/ao-logs 22 | - /tmp/ansible-operator/runner 23 | - stdout 24 | image: "{{ operator_image }}" 25 | imagePullPolicy: "{{ pull_policy|default('Always') }}" 26 | volumeMounts: 27 | - mountPath: /tmp/ansible-operator/runner 28 | name: runner 29 | readOnly: true 30 | - name: operator 31 | image: "{{ operator_image }}" 32 | imagePullPolicy: "{{ pull_policy|default('Always') }}" 33 | volumeMounts: 34 | - mountPath: /tmp/ansible-operator/runner 35 | name: runner 36 | env: 37 | # Watch all namespaces (cluster-scoped). 38 | - name: WATCH_NAMESPACE 39 | value: "" 40 | - name: POD_NAME 41 | valueFrom: 42 | fieldRef: 43 | fieldPath: metadata.name 44 | - name: OPERATOR_NAME 45 | value: "drupal-operator" 46 | volumes: 47 | - name: runner 48 | emptyDir: {} 49 | -------------------------------------------------------------------------------- /deploy/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: drupal-operator 7 | rules: 8 | - apiGroups: 9 | - "" 10 | resources: 11 | - pods 12 | - services 13 | - endpoints 14 | - persistentvolumeclaims 15 | - events 16 | - configmaps 17 | - secrets 18 | verbs: 19 | - '*' 20 | - apiGroups: 21 | - apps 22 | resources: 23 | - deployments 24 | - daemonsets 25 | - replicasets 26 | - statefulsets 27 | verbs: 28 | - '*' 29 | - apiGroups: 30 | - extensions 31 | resources: 32 | - ingresses 33 | verbs: 34 | - '*' 35 | - apiGroups: 36 | - monitoring.coreos.com 37 | resources: 38 | - servicemonitors 39 | verbs: 40 | - get 41 | - create 42 | - apiGroups: 43 | - apps 44 | resourceNames: 45 | - drupal-operator 46 | resources: 47 | - deployments/finalizers 48 | verbs: 49 | - update 50 | - apiGroups: 51 | - "" 52 | resources: 53 | - pods 54 | verbs: 55 | - get 56 | - apiGroups: 57 | - apps 58 | resources: 59 | - replicasets 60 | verbs: 61 | - get 62 | - apiGroups: 63 | - drupal.drupal.org 64 | resources: 65 | - '*' 66 | verbs: 67 | - '*' 68 | -------------------------------------------------------------------------------- /deploy/role_binding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: ClusterRoleBinding 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: drupal-operator 6 | subjects: 7 | - kind: ServiceAccount 8 | name: drupal-operator 9 | namespace: default 10 | roleRef: 11 | kind: ClusterRole 12 | name: drupal-operator 13 | apiGroup: rbac.authorization.k8s.io 14 | -------------------------------------------------------------------------------- /deploy/service_account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: drupal-operator 6 | namespace: default 7 | -------------------------------------------------------------------------------- /main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | gather_facts: no 4 | roles: 5 | - drupal 6 | -------------------------------------------------------------------------------- /molecule/default/asserts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: k8s 4 | gather_facts: false 5 | 6 | vars: 7 | ansible_python_interpreter: '{{ ansible_playbook_python }}' 8 | deploy_dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/deploy" 9 | custom_resource: "{{ lookup('file', '/'.join([deploy_dir, 'crds/drupal_v1alpha1_drupal_cr.yaml'])) | from_yaml }}" 10 | 11 | tasks: 12 | - name: Get all pods in {{ custom_resource.metadata.namespace }} 13 | k8s_info: 14 | api_version: v1 15 | kind: Pod 16 | namespace: '{{ custom_resource.metadata.namespace }}' 17 | register: pods 18 | delegate_to: localhost 19 | 20 | - name: Output pods 21 | debug: var=pods 22 | 23 | - name: Get drupal service 24 | k8s: 25 | kind: Service 26 | api_version: v1 27 | name: "{{ custom_resource.metadata.name }}" 28 | namespace: "{{ custom_resource.metadata.namespace }}" 29 | register: drupal_service 30 | delegate_to: localhost 31 | 32 | - name: Verify Drupal installer loads 33 | uri: 34 | url: "http://localhost:{{ drupal_service.result.spec.ports[0].nodePort }}/core/install.php" 35 | status_code: 200 36 | register: result 37 | until: (result.status|default(-1)) == 200 38 | retries: 60 39 | delay: 5 40 | vars: 41 | ansible_python_interpreter: python3 42 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: localhost 4 | connection: local 5 | 6 | vars: 7 | ansible_python_interpreter: '{{ ansible_playbook_python }}' 8 | 9 | roles: 10 | - drupal 11 | 12 | - import_playbook: '{{ playbook_dir }}/asserts.yml' 13 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: docker 6 | lint: | 7 | set -e 8 | yamllint . 9 | ansible-lint 10 | platforms: 11 | - name: kind-default 12 | groups: 13 | - k8s 14 | image: bsycorp/kind:latest-1.15 15 | privileged: True 16 | override_command: no 17 | exposed_ports: 18 | - 8443/tcp 19 | - 10080/tcp 20 | published_ports: 21 | - 0.0.0.0:${TEST_CLUSTER_PORT:-9443}:8443/tcp 22 | pre_build_image: yes 23 | provisioner: 24 | name: ansible 25 | log: True 26 | inventory: 27 | group_vars: 28 | all: 29 | operator_namespace: ${TEST_NAMESPACE:-default} 30 | env: 31 | K8S_AUTH_KUBECONFIG: /tmp/molecule/kind-default/kubeconfig 32 | KUBECONFIG: /tmp/molecule/kind-default/kubeconfig 33 | ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/roles 34 | KIND_PORT: '${TEST_CLUSTER_PORT:-9443}' 35 | -------------------------------------------------------------------------------- /molecule/default/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare operator resources 3 | hosts: localhost 4 | connection: local 5 | 6 | vars: 7 | ansible_python_interpreter: '{{ ansible_playbook_python }}' 8 | deploy_dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/deploy" 9 | 10 | tasks: 11 | - name: Create Custom Resource Definition 12 | k8s: 13 | definition: "{{ lookup('file', '/'.join([deploy_dir, 'crds/drupal_v1alpha1_drupal_crd.yaml'])) }}" 14 | 15 | - name: Ensure specified namespace is present 16 | k8s: 17 | api_version: v1 18 | kind: Namespace 19 | name: '{{ operator_namespace }}' 20 | 21 | - name: Create RBAC resources 22 | k8s: 23 | definition: "{{ lookup('template', '/'.join([deploy_dir, item])) }}" 24 | namespace: '{{ operator_namespace }}' 25 | with_items: 26 | - role.yaml 27 | - role_binding.yaml 28 | - service_account.yaml 29 | -------------------------------------------------------------------------------- /molecule/test-local/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Build Operator in Kind container 3 | hosts: k8s 4 | 5 | vars: 6 | image_name: drupal.drupal.org/drupal-operator:testing 7 | 8 | tasks: 9 | # using command so we don't need to install any dependencies 10 | - name: Get existing image hash 11 | command: docker images -q {{ image_name }} 12 | register: prev_hash 13 | changed_when: false 14 | 15 | - name: Build Operator Image 16 | command: docker build -f /build/build/Dockerfile -t {{ image_name }} /build 17 | register: build_cmd 18 | changed_when: not prev_hash.stdout or (prev_hash.stdout and prev_hash.stdout not in ''.join(build_cmd.stdout_lines[-2:])) 19 | 20 | - name: Converge 21 | hosts: localhost 22 | connection: local 23 | 24 | vars: 25 | ansible_python_interpreter: '{{ ansible_playbook_python }}' 26 | deploy_dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/deploy" 27 | pull_policy: Never 28 | operator_image: drupal.drupal.org/drupal-operator:testing 29 | custom_resource: "{{ lookup('file', '/'.join([deploy_dir, 'crds/drupal_v1alpha1_drupal_cr.yaml'])) | from_yaml }}" 30 | 31 | tasks: 32 | 33 | - block: 34 | 35 | - name: Delete the Operator Deployment 36 | k8s: 37 | state: absent 38 | namespace: '{{ operator_namespace }}' 39 | definition: "{{ lookup('template', '/'.join([deploy_dir, 'operator.yaml'])) }}" 40 | register: delete_deployment 41 | when: hostvars[groups.k8s.0].build_cmd.changed 42 | 43 | - name: Wait 30s for Operator Deployment to terminate 44 | k8s_info: 45 | api_version: '{{ definition.apiVersion }}' 46 | kind: '{{ definition.kind }}' 47 | namespace: '{{ operator_namespace }}' 48 | name: '{{ definition.metadata.name }}' 49 | vars: 50 | definition: "{{ lookup('template', '/'.join([deploy_dir, 'operator.yaml'])) | from_yaml }}" 51 | register: deployment 52 | until: not deployment.resources 53 | delay: 3 54 | retries: 10 55 | when: delete_deployment.changed 56 | 57 | - name: Create the Operator Deployment 58 | k8s: 59 | namespace: '{{ operator_namespace }}' 60 | definition: "{{ lookup('template', '/'.join([deploy_dir, 'operator.yaml'])) }}" 61 | 62 | - name: Ensure the Drupal custom_resource namespace exists 63 | k8s: 64 | state: present 65 | name: '{{ custom_resource.metadata.namespace }}' 66 | kind: Namespace 67 | api_version: v1 68 | 69 | - name: Create the drupal.drupal.org/v1alpha1.Drupal 70 | k8s: 71 | state: present 72 | namespace: '{{ custom_resource.metadata.namespace }}' 73 | definition: '{{ custom_resource }}' 74 | 75 | - name: Wait 5m for reconciliation to run 76 | k8s_info: 77 | api_version: '{{ custom_resource.apiVersion }}' 78 | kind: '{{ custom_resource.kind }}' 79 | namespace: '{{ custom_resource.metadata.namespace }}' 80 | name: '{{ custom_resource.metadata.name }}' 81 | register: cr 82 | until: 83 | - "'Successful' in (cr | json_query('resources[].status.conditions[].reason'))" 84 | delay: 6 85 | retries: 50 86 | 87 | rescue: 88 | 89 | - name: debug cr 90 | ignore_errors: yes 91 | failed_when: false 92 | debug: 93 | var: debug_cr 94 | vars: 95 | debug_cr: '{{ lookup("k8s", 96 | kind=custom_resource.kind, 97 | api_version=custom_resource.apiVersion, 98 | namespace=custom_resource.metadata.namespace, 99 | resource_name=custom_resource.metadata.name 100 | )}}' 101 | 102 | - name: debug drupal deployment 103 | ignore_errors: yes 104 | failed_when: false 105 | debug: 106 | var: deploy 107 | vars: 108 | deploy: '{{ lookup("k8s", 109 | kind="Deployment", 110 | api_version="apps/v1", 111 | namespace=custom_resource.metadata.namespace, 112 | label_selector="app=drupal" 113 | )}}' 114 | 115 | - name: get operator logs 116 | ignore_errors: yes 117 | failed_when: false 118 | command: kubectl logs deployment/{{ definition.metadata.name }} -n {{ operator_namespace }} -c operator 119 | environment: 120 | KUBECONFIG: '{{ lookup("env", "KUBECONFIG") }}' 121 | vars: 122 | definition: "{{ lookup('template', '/'.join([deploy_dir, 'operator.yaml'])) | from_yaml }}" 123 | register: log 124 | 125 | - name: print debug output 126 | debug: var=log.stdout_lines 127 | 128 | - name: fail if converge didn't succeed 129 | fail: 130 | msg: "Failed on action: converge" 131 | 132 | - import_playbook: '{{ playbook_dir }}/../default/asserts.yml' 133 | -------------------------------------------------------------------------------- /molecule/test-local/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: docker 6 | lint: | 7 | set -e 8 | yamllint . 9 | ansible-lint 10 | platforms: 11 | - name: kind-test-local 12 | groups: 13 | - k8s 14 | image: bsycorp/kind:latest-1.15 15 | privileged: True 16 | override_command: no 17 | exposed_ports: 18 | - 8443/tcp 19 | - 10080/tcp 20 | published_ports: 21 | - 0.0.0.0:${TEST_CLUSTER_PORT:-10443}:8443/tcp 22 | pre_build_image: yes 23 | volumes: 24 | - ${MOLECULE_PROJECT_DIRECTORY}:/build:Z 25 | provisioner: 26 | name: ansible 27 | log: True 28 | inventory: 29 | group_vars: 30 | all: 31 | operator_namespace: ${TEST_NAMESPACE:-default} 32 | env: 33 | K8S_AUTH_KUBECONFIG: /tmp/molecule/kind-test-local/kubeconfig 34 | KUBECONFIG: /tmp/molecule/kind-test-local/kubeconfig 35 | ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/roles 36 | KIND_PORT: '${TEST_CLUSTER_PORT:-10443}' 37 | scenario: 38 | test_sequence: 39 | - lint 40 | - destroy 41 | - dependency 42 | - syntax 43 | - create 44 | - prepare 45 | - converge 46 | - side_effect 47 | - verify 48 | - destroy 49 | -------------------------------------------------------------------------------- /molecule/test-local/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare kubernetes environment 3 | hosts: k8s 4 | gather_facts: no 5 | 6 | vars: 7 | kubeconfig: "{{ lookup('env', 'KUBECONFIG') }}" 8 | 9 | tasks: 10 | - name: delete the kubeconfig if present 11 | file: 12 | path: '{{ kubeconfig }}' 13 | state: absent 14 | delegate_to: localhost 15 | 16 | - name: Fetch the kubeconfig 17 | fetch: 18 | dest: '{{ kubeconfig }}' 19 | flat: true 20 | src: /root/.kube/config 21 | 22 | - name: Change the kubeconfig port to the proper value 23 | replace: 24 | regexp: '8443' 25 | replace: "{{ lookup('env', 'KIND_PORT') }}" 26 | path: '{{ kubeconfig }}' 27 | mode: 0644 28 | delegate_to: localhost 29 | 30 | - name: Wait for the Kubernetes API to become available (this could take a minute) 31 | uri: 32 | url: "http://localhost:10080/kubernetes-ready" 33 | status_code: 200 34 | validate_certs: no 35 | register: result 36 | until: (result.status|default(-1)) == 200 37 | retries: 60 38 | delay: 5 39 | 40 | - import_playbook: ../default/prepare.yml 41 | -------------------------------------------------------------------------------- /molecule/test-minikube/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TODO: For some reason prepare is not run after a destroy in the Minikube env. 3 | - import_playbook: ../default/prepare.yml 4 | 5 | - name: Build Operator in Minikube 6 | hosts: localhost 7 | connection: local 8 | 9 | vars: 10 | image_name: drupal.drupal.org/drupal-operator:testing 11 | 12 | tasks: 13 | # Use raw Docker commands inside Minikube to avoid extra Python dependencies. 14 | - name: Get existing image hash 15 | shell: | 16 | eval $(minikube docker-env) 17 | docker images -q {{ image_name }} 18 | register: prev_hash 19 | changed_when: false 20 | 21 | - name: Build Operator Image 22 | shell: | 23 | eval $(minikube docker-env) 24 | docker build -f ../../build/Dockerfile -t {{ image_name }} ../.. 25 | register: build_cmd 26 | changed_when: not prev_hash.stdout or (prev_hash.stdout and prev_hash.stdout not in ''.join(build_cmd.stdout_lines[-2:])) 27 | 28 | - name: Converge 29 | hosts: localhost 30 | connection: local 31 | 32 | vars: 33 | ansible_python_interpreter: '{{ ansible_playbook_python }}' 34 | deploy_dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/deploy" 35 | pull_policy: Never 36 | operator_image: drupal.drupal.org/drupal-operator:testing 37 | custom_resource: "{{ lookup('file', '/'.join([deploy_dir, 'crds/drupal_v1alpha1_drupal_cr.yaml'])) | from_yaml }}" 38 | 39 | tasks: 40 | - block: 41 | - name: Delete the Operator Deployment 42 | k8s: 43 | state: absent 44 | namespace: '{{ operator_namespace }}' 45 | definition: "{{ lookup('template', '/'.join([deploy_dir, 'operator.yaml'])) }}" 46 | register: delete_deployment 47 | when: build_cmd.changed 48 | 49 | - name: Wait 30s for Operator Deployment to terminate 50 | k8s_info: 51 | api_version: '{{ definition.apiVersion }}' 52 | kind: '{{ definition.kind }}' 53 | namespace: '{{ operator_namespace }}' 54 | name: '{{ definition.metadata.name }}' 55 | vars: 56 | definition: "{{ lookup('template', '/'.join([deploy_dir, 'operator.yaml'])) | from_yaml }}" 57 | register: deployment 58 | until: not deployment.resources 59 | delay: 3 60 | retries: 10 61 | when: delete_deployment.changed 62 | 63 | - name: Create the Operator Deployment 64 | k8s: 65 | namespace: '{{ operator_namespace }}' 66 | definition: "{{ lookup('template', '/'.join([deploy_dir, 'operator.yaml'])) }}" 67 | 68 | - name: Ensure the Drupal custom_resource namespace exists 69 | k8s: 70 | state: present 71 | name: '{{ custom_resource.metadata.namespace }}' 72 | kind: Namespace 73 | api_version: v1 74 | 75 | - name: Create the drupal.drupal.org/v1alpha1.Drupal 76 | k8s: 77 | state: present 78 | namespace: '{{ custom_resource.metadata.namespace }}' 79 | definition: '{{ custom_resource }}' 80 | 81 | - name: Wait 15m for reconciliation to run 82 | k8s_info: 83 | api_version: '{{ custom_resource.apiVersion }}' 84 | kind: '{{ custom_resource.kind }}' 85 | namespace: '{{ custom_resource.metadata.namespace }}' 86 | name: '{{ custom_resource.metadata.name }}' 87 | register: cr 88 | until: 89 | - "'Successful' in (cr | json_query('resources[].status.conditions[].reason'))" 90 | delay: 6 91 | retries: 150 92 | 93 | rescue: 94 | 95 | - name: debug cr 96 | ignore_errors: yes 97 | failed_when: false 98 | debug: 99 | var: debug_cr 100 | vars: 101 | debug_cr: '{{ lookup("k8s", 102 | kind=custom_resource.kind, 103 | api_version=custom_resource.apiVersion, 104 | namespace=custom_resource.metadata.namespace, 105 | resource_name=custom_resource.metadata.name 106 | )}}' 107 | 108 | - name: debug drupal deployment 109 | ignore_errors: yes 110 | failed_when: false 111 | debug: 112 | var: deploy 113 | vars: 114 | deploy: '{{ lookup("k8s", 115 | kind="Deployment", 116 | api_version="apps/v1", 117 | namespace=custom_resource.metadata.namespace, 118 | label_selector="app=drupal" 119 | )}}' 120 | 121 | - name: get operator logs 122 | ignore_errors: yes 123 | failed_when: false 124 | command: kubectl logs deployment/{{ definition.metadata.name }} -n {{ operator_namespace }} -c operator 125 | environment: 126 | KUBECONFIG: '{{ lookup("env", "KUBECONFIG") }}' 127 | vars: 128 | definition: "{{ lookup('template', '/'.join([deploy_dir, 'operator.yaml'])) | from_yaml }}" 129 | register: log 130 | 131 | - name: print debug output 132 | debug: var=log.stdout_lines 133 | 134 | - name: fail if converge didn't succeed 135 | fail: 136 | msg: "Failed on action: converge" 137 | 138 | - import_playbook: '{{ playbook_dir }}/../default/asserts.yml' 139 | -------------------------------------------------------------------------------- /molecule/test-minikube/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: delegated 6 | options: 7 | managed: False 8 | ansible_connection_options: {} 9 | lint: | 10 | set -e 11 | yamllint . 12 | ansible-lint 13 | platforms: 14 | - name: test-minikube 15 | groups: 16 | - k8s 17 | provisioner: 18 | name: ansible 19 | inventory: 20 | group_vars: 21 | all: 22 | operator_namespace: ${TEST_NAMESPACE:-default} 23 | env: 24 | ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/roles 25 | scenario: 26 | test_sequence: 27 | - lint 28 | - destroy 29 | - dependency 30 | - syntax 31 | - create 32 | - prepare 33 | - converge 34 | - side_effect 35 | - verify 36 | - destroy 37 | -------------------------------------------------------------------------------- /molecule/test-minikube/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_playbook: ../default/prepare.yml 3 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | collections: 3 | - community.kubernetes 4 | - operator_sdk.util 5 | -------------------------------------------------------------------------------- /roles/drupal/README.md: -------------------------------------------------------------------------------- 1 | Drupal K8s Role 2 | ========= 3 | 4 | This role configures an instance of Drupal inside of a Kubernetes cluster and namespace. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Requires the `openshift` Python library to interact with Kubernetes: `pip install openshift`. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | TODO. 15 | 16 | Dependencies 17 | ------------ 18 | 19 | N/A 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | ``` 25 | --- 26 | - hosts: k8s 27 | roles: 28 | - drupal 29 | ``` 30 | 31 | License 32 | ------- 33 | 34 | BSD 35 | 36 | Author Information 37 | ------------------ 38 | 39 | [Jeff Geerling](https://www.jeffgeerling.com). 40 | -------------------------------------------------------------------------------- /roles/drupal/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | drupal_image: 'drupal:8.8-apache' 3 | # You should generate your own hash salt, e.g. `Crypt::randomBytesBase64(55)`. 4 | drupal_hash_salt: '' 5 | drupal_trusted_host_patterns: '' 6 | files_pvc_size: 1Gi 7 | 8 | # Database options (will be used regardless of database location). 9 | database_name: drupal 10 | database_username: drupal 11 | database_password: drupal 12 | database_host: 'example-drupal-mariadb' 13 | 14 | # Set this to 'true' to use a single-pod database managed by this operator. 15 | manage_database: true 16 | database_image: mariadb:10 17 | database_pvc_size: 1Gi 18 | 19 | # Set this to 'true' to have this operator manage Ingress for the site. 20 | manage_ingress: true 21 | drupal_hostname: example-drupal.test 22 | -------------------------------------------------------------------------------- /roles/drupal/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Jeff Geerling 4 | description: Drupal deployment inside Kubernetes. 5 | company: Midwestern Mac, LLC 6 | 7 | license: MIT 8 | 9 | min_ansible_version: 2.8 10 | 11 | platforms: 12 | - name: EL 13 | versions: 14 | - all 15 | - name: Debian 16 | versions: 17 | - all 18 | 19 | galaxy_tags: 20 | - drupal 21 | - cms 22 | - web 23 | - application 24 | - website 25 | - operator 26 | 27 | dependencies: [] 28 | -------------------------------------------------------------------------------- /roles/drupal/tasks/database.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Specify whether database resources should be present. 3 | set_fact: 4 | db_resource_state: "{{ 'present' if manage_database else 'absent' }}" 5 | 6 | - name: MariaDB PVC. 7 | k8s: 8 | state: '{{ db_resource_state }}' 9 | definition: 10 | kind: PersistentVolumeClaim 11 | apiVersion: v1 12 | metadata: 13 | name: '{{ meta.name }}-db-pvc' 14 | namespace: '{{ meta.namespace }}' 15 | spec: 16 | accessModes: 17 | - ReadWriteOnce 18 | resources: 19 | requests: 20 | storage: '{{ database_pvc_size }}' 21 | 22 | - name: MariaDB Deployment. 23 | k8s: 24 | state: '{{ db_resource_state }}' 25 | definition: 26 | kind: Deployment 27 | apiVersion: apps/v1 28 | metadata: 29 | name: '{{ meta.name }}-mariadb' 30 | namespace: '{{ meta.namespace }}' 31 | spec: 32 | replicas: 1 33 | selector: 34 | matchLabels: 35 | app: mariadb 36 | template: 37 | metadata: 38 | labels: 39 | app: mariadb 40 | spec: 41 | containers: 42 | - name: mariadb 43 | image: '{{ database_image }}' 44 | ports: 45 | - containerPort: 3306 46 | env: 47 | - name: MYSQL_DATABASE 48 | value: '{{ database_name }}' 49 | - name: MYSQL_USER 50 | value: '{{ database_username }}' 51 | - name: MYSQL_PASSWORD 52 | value: '{{ database_password }}' 53 | - name: MYSQL_RANDOM_ROOT_PASSWORD 54 | value: "yes" 55 | volumeMounts: 56 | - mountPath: /var/lib/mysql/ 57 | name: drupal-db 58 | volumes: 59 | - name: drupal-db 60 | persistentVolumeClaim: 61 | claimName: '{{ meta.name }}-db-pvc' 62 | 63 | - name: MariaDB Service 64 | k8s: 65 | state: '{{ db_resource_state }}' 66 | definition: 67 | kind: Service 68 | apiVersion: v1 69 | metadata: 70 | name: '{{ meta.name }}-mariadb' 71 | namespace: '{{ meta.namespace }}' 72 | spec: 73 | ports: 74 | - port: 3306 75 | targetPort: 3306 76 | selector: 77 | app: 'mariadb' 78 | -------------------------------------------------------------------------------- /roles/drupal/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Drupal Settings ConfigMap. 3 | k8s: 4 | definition: 5 | kind: ConfigMap 6 | apiVersion: v1 7 | metadata: 8 | name: '{{ meta.name }}-drupal-config' 9 | namespace: '{{ meta.namespace }}' 10 | data: 11 | settings.php: |- 12 | '{{ database_name }}', 16 | 'username' => '{{ database_username }}', 17 | 'password' => '{{ database_password }}', 18 | 'prefix' => '', 19 | 'host' => '{{ database_host }}', 20 | 'port' => '3306', 21 | 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql', 22 | 'driver' => 'mysql', 23 | ); 24 | 25 | $settings['hash_salt'] = '{{ drupal_hash_salt | default(lookup('password', '/dev/null chars=ascii_letters'), true) }}'; 26 | $settings['trusted_host_patterns'] = ['{{ drupal_trusted_host_patterns | default('^.+$', true) }}']; 27 | 28 | - name: Drupal files PVC. 29 | k8s: 30 | definition: 31 | kind: PersistentVolumeClaim 32 | apiVersion: v1 33 | metadata: 34 | name: '{{ meta.name }}-files-pvc' 35 | namespace: '{{ meta.namespace }}' 36 | spec: 37 | accessModes: 38 | - ReadWriteOnce 39 | resources: 40 | requests: 41 | storage: '{{ files_pvc_size }}' 42 | 43 | - name: Drupal Deployment. 44 | k8s: 45 | definition: 46 | kind: Deployment 47 | apiVersion: apps/v1 48 | metadata: 49 | name: '{{ meta.name }}-drupal' 50 | namespace: '{{ meta.namespace }}' 51 | spec: 52 | replicas: 1 53 | selector: 54 | matchLabels: 55 | app: drupal 56 | template: 57 | metadata: 58 | labels: 59 | app: drupal 60 | spec: 61 | containers: 62 | - name: drupal 63 | image: "{{ drupal_image }}" 64 | ports: 65 | - containerPort: 80 66 | livenessProbe: 67 | tcpSocket: 68 | port: 80 69 | initialDelaySeconds: 60 70 | readinessProbe: 71 | tcpSocket: 72 | port: 80 73 | initialDelaySeconds: 30 74 | volumeMounts: 75 | - mountPath: /var/www/html/sites/default/ 76 | name: drupal-settings 77 | - mountPath: /var/www/html/sites/default/files/ 78 | name: drupal-files 79 | volumes: 80 | - name: drupal-settings 81 | configMap: 82 | name: '{{ meta.name }}-drupal-config' 83 | - name: drupal-files 84 | persistentVolumeClaim: 85 | claimName: '{{ meta.name }}-files-pvc' 86 | 87 | - name: Drupal Service 88 | k8s: 89 | definition: 90 | kind: Service 91 | apiVersion: v1 92 | metadata: 93 | name: '{{ meta.name }}' 94 | namespace: '{{ meta.namespace }}' 95 | spec: 96 | type: NodePort 97 | ports: 98 | - port: 80 99 | targetPort: 80 100 | selector: 101 | app: 'drupal' 102 | 103 | - name: Drupal Ingress 104 | k8s: 105 | state: "{{ 'present' if manage_ingress else 'absent' }}" 106 | definition: 107 | apiVersion: extensions/v1beta1 108 | kind: Ingress 109 | metadata: 110 | name: '{{ meta.name }}' 111 | namespace: '{{ meta.namespace }}' 112 | spec: 113 | rules: 114 | - host: '{{ drupal_hostname }}' 115 | http: 116 | paths: 117 | - path: / 118 | backend: 119 | serviceName: '{{ meta.name }}' 120 | servicePort: 80 121 | 122 | - include_tasks: database.yml 123 | -------------------------------------------------------------------------------- /watches.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - version: v1alpha1 3 | group: drupal.drupal.org 4 | kind: Drupal 5 | playbook: /opt/ansible/main.yml 6 | --------------------------------------------------------------------------------