chore(pulumi/examples): showcase gitlab's omnibus installation on an ec2 instance

This commit is contained in:
Michele Cereda
2024-04-28 22:23:39 +02:00
parent 3cbce8c0db
commit 38e5d9803f
22 changed files with 4046 additions and 4 deletions

View File

@@ -1,3 +1,6 @@
ansible/playbooks/aws.ec2.enable-ssm-agent.yml package-latest
ansible/playbooks/keybase.register-device.yml no-changed-when
examples/ansible/aws_ec2.yml yaml[comments-indentation]
ansible/playbooks/aws.ec2.enable-ssm-agent.yml package-latest
ansible/playbooks/keybase.register-device.yml no-changed-when
examples/ansible/aws_ec2.yml yaml[comments-indentation]
examples/pulumi/gitlab-omnibus-on-aws-ec2/ansible-playbook.yml package-latest
examples/pulumi/gitlab-omnibus-on-aws-ec2/ansible-role-gitlab-omnibus-on-ec2/tasks/configure/omnibus.yml no-handler
examples/pulumi/gitlab-omnibus-on-aws-ec2/ansible-role-gitlab-omnibus-on-ec2/tasks/pre-flight.yml name[template]

View File

@@ -0,0 +1,3 @@
/bin/
/node_modules/
/package-lock.json

View File

@@ -0,0 +1,15 @@
#!make
override inventory ?= aws_ec2.yml
override playbook ?= ansible-playbook.yml
override venv ?= .venv
create-venv: python_version ?= 3
create-venv: ${shell which 'python${python_version}'}
@python${python_version} -m 'venv' '${venv}'
@${venv}/bin/pip --require-virtualenv install -U -r 'requirements.txt'
check: ${venv}/bin/ansible-playbook
@${venv}/bin/ansible-playbook -i '${inventory}' -DCvvv '${playbook}'
run: ${venv}/bin/ansible-playbook
@${venv}/bin/ansible-playbook -i '${inventory}' -D '${playbook}'

View File

@@ -0,0 +1,7 @@
encryptionsalt: v1:C8omJ1iHsso=:v1:6+WUBXXf7EybHcNa:0UOUSLZ3p58s7/o+aNHOKJ6hO2Nz2A==
config:
acme:serverUrl: https://acme-v02.api.letsencrypt.org/directory
aws:defaultTags:
tags:
ManagedBy: Pulumi
PulumiProject: gitlab-omnibus-on-aws-ec2

View File

@@ -0,0 +1,9 @@
name: gitlab-omnibus-on-aws-ec2
runtime: nodejs
description: Gitlab Omnibus on AWS EC2
config:
pulumi:tags:
value:
pulumi:template: typescript
backend:
url: file://.

View File

@@ -0,0 +1,45 @@
---
- name: Install and configure Gitlab
hosts: tag_Name_Gitlab_Omnibus
vars:
ansible_connection: community.aws.aws_ssm
ansible_aws_ssm_region: eu-east-2
handlers:
- name: "Start AWS' SSM agent"
tags:
- aws
- service
- ssm
become: true
ansible.builtin.service:
name: amazon-ssm-agent.service
state: started
enabled: true
pre_tasks:
- name: Update the system
tags:
- maintenance
- never
become: true
ansible.builtin.package:
name: '*'
state: latest
- name: Install required packages
tags: maintenance
become: true
ansible.builtin.package:
name:
- amazon-ssm-agent
- ruby
- vim
notify: "Start AWS' SSM agent"
roles:
- role: ansible-role-gitlab-omnibus-on-ec2
vars:
external_url: 'https://gitlab.company.com'
# initial_password: null
# install_method: 'omnibus'
# Use the custom certificates from dns challenge, see 'userData' in pulumi code.
# letsencrypt_enabled: false

View File

@@ -0,0 +1,7 @@
---
install_method: omnibus
external_url: https://{{ ansible_fqdn }}
initial_password: null
letsencrypt_enabled: false

View File

@@ -0,0 +1,23 @@
[gitlab_gitlab-ee]
name=gitlab_gitlab-ee
baseurl=https://packages.gitlab.com/gitlab/gitlab-ee/amazon/2023/$basearch
repo_gpgcheck=1
gpgcheck=1
enabled=1
gpgkey=https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey
https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey/gitlab-gitlab-ee-3D645A26AB9FBD22.pub.gpg
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300
[gitlab_gitlab-ee-source]
name=gitlab_gitlab-ee-source
baseurl=https://packages.gitlab.com/gitlab/gitlab-ee/amazon/2023/SRPMS
repo_gpgcheck=1
gpgcheck=1
enabled=1
gpgkey=https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey
https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey/gitlab-gitlab-ee-3D645A26AB9FBD22.pub.gpg
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300

View File

@@ -0,0 +1,4 @@
---
collections:
- community.dns

View File

@@ -0,0 +1,41 @@
---
- name: Create the configuration file
become: true
ansible.builtin.template:
src: gitlab.rb.j2
dest: /etc/gitlab/gitlab.rb
owner: root
group: root
mode: u=rw,g=,o=
backup: true
register: config_file
- name: Validate the configuration file
# Since it cannot be validated by the 'template' module as it requires a file name to be specified.
when: config_file is changed
become: true
ansible.builtin.command: >-
gitlab-ctl show-config
register: config_file_validation
changed_when: false
failed_when: config_file_validation.rc != 0
- name: Reconfigure Gitlab
when:
- config_file is changed
- config_file_validation is not failed
become: true
ansible.builtin.command: >-
gitlab-ctl reconfigure
register: reconfiguration
changed_when:
- reconfiguration.rc == 0
- >-
{{
(
reconfiguration.stdout
| regex_findall('Infra Phase complete, .*')
) is not search('0/')
}}
failed_when: reconfiguration.rc != 0

View File

@@ -0,0 +1,93 @@
---
# Follow 'https://about.gitlab.com/install/#amazonlinux-2023'.
- name: Add Gitlab's repositories
tags:
- repo
- repository
- repositories
become: true
block:
# Refer 'files/gitlab_gitlab-ee.repo'.
- name: Add Gitlab's package repository
ansible.builtin.yum_repository:
name: gitlab-ee
description: gitlab-ee
baseurl: https://packages.gitlab.com/gitlab/gitlab-ee/amazon/2023/$basearch
repo_gpgcheck: true
gpgcheck: true
gpgkey: |-
https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey
https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey/gitlab-gitlab-ee-3D645A26AB9FBD22.pub.gpg
sslverify: true
sslcacert: /etc/pki/tls/certs/ca-bundle.crt
metadata_expire: 300
- name: Add Gitlab's sources repository
ansible.builtin.yum_repository:
name: gitlab-ee-source
description: gitlab-ee-source
baseurl: https://packages.gitlab.com/gitlab/gitlab-ee/amazon/2023/SRPMS
repo_gpgcheck: true
gpgcheck: true
gpgkey: |-
https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey
https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey/gitlab-gitlab-ee-3D645A26AB9FBD22.pub.gpg
sslverify: true
sslcacert: /etc/pki/tls/certs/ca-bundle.crt
metadata_expire: 300
- name: Install Gitlab's omnibus package
tags:
- package
become: true
environment:
EXTERNAL_URL: "{{ external_url }}"
GITLAB_ROOT_PASSWORD: "{{ initial_password | ternary(initial_password, omit, omit) }}"
ansible.builtin.package:
name: gitlab-ee
- name: Print the administrator's credentials
tags:
- credentials
- password
block:
- name: Check whether the auto-generated administrator's initial password file exists
ansible.builtin.stat:
path: /etc/gitlab/initial_root_password
register: initial_password_file_stat
- name: Recover the auto-generated administrator's initial password
block:
- name: Read the initial password file
when: initial_password_file_stat.stat.exists
block:
- name: Read the initial password file
become: true
ansible.builtin.slurp:
src: /etc/gitlab/initial_root_password
register: initial_password_file
- name: Save the initial login credentials
ansible.builtin.set_fact:
initial_password: |-
{{
initial_password_file['content']
| b64decode
| regex_findall('Password: .*')
| first
| split(': ')
| last
}}
- name: Report that the password is not available anymore
when: not initial_password_file_stat.stat.exists
ansible.builtin.set_fact:
initial_password: NOT_AVAILABLE_ANYMORE
- name: Print the administrator's credentials
ansible.builtin.debug:
msg: >-
{{
dict([
[ 'URL', external_url ],
[ 'Username', 'root' ],
[ 'Initial Password', initial_password ]
])
}}

View File

@@ -0,0 +1,25 @@
---
- name: Pre-flight checks
tags:
- check
- checks
- pre-flight
- preflight
ansible.builtin.import_tasks:
file: pre-flight.yml
- name: Install Gitlab
tags:
- "{{ install_method }}"
- gitlab
- install
ansible.builtin.import_tasks:
file: "{{ role_path }}/tasks/install/{{ install_method }}.yml"
- name: Configure Gitlab
tags:
- "{{ install_method }}"
- configure
- gitlab
ansible.builtin.import_tasks:
file: "{{ role_path }}/tasks/configure/{{ install_method }}.yml"

View File

@@ -0,0 +1,49 @@
---
- name: Check the requested install method is supported by the role
ansible.builtin.assert:
that: install_method in supported_install_methods
fail_msg: >-
Install method '{{ install_method }}' not supported by the role, 'install_method' must be one of
{{ supported_install_methods }}
success_msg: Install method '{{ install_method }}' supported by the role
- name: Check the initial password is null or a valid string
ansible.builtin.assert:
that: initial_password != ''
fail_msg: Initial password setting not supported by the role, 'initial_password' must be either null or not empty
success_msg: Initial password setting supported by the role
- name: Check the given external URL is valid
block:
- name: Check the external URL is a valid URL
ansible.builtin.assert:
that: external_url is ansible.builtin.url
fail_msg: External URL '{{ external_url }}' is not a valid URL, set 'external_url' to a valid one
success_msg: External URL '{{ external_url }}' is a valid URL
- name: Check the external URL's scheme is supported by the role
ansible.builtin.assert:
that: external_url_scheme in supported_external_url_schemes
fail_msg: >-
External URL scheme '{{ external_url_scheme }}' not supported by the role, set 'external_url' to have one of
{{ supported_external_url_schemes }}
success_msg: External URL scheme '{{ external_url_scheme }}' supported by the role
- name: "Check the DNS entries required by Let's Encrypt exist"
when: letsencrypt_enabled
block:
- name: AAAA
tags:
- debug
- never
ansible.builtin.debug:
msg: "{{ query('community.dns.lookup', external_url_hostname) }}"
- name: Check an A or AAAA DNS record for '{{ external_url_hostname }}' exist
ansible.builtin.assert:
that: >-
query('community.dns.lookup', external_url_hostname) != [] or
query('community.dns.lookup', external_url_hostname, type='AAAA') != []
fail_msg: >-
Let's Encrypt feature enabled but no DNS entry of type 'A' or 'AAAA' found for '{{ external_url_hostname }}',
create one first
success_msg: Required DNS entry found for '{{ external_url_hostname }}'

View File

@@ -0,0 +1,12 @@
## GitLab configuration settings
## -------------------------------------
## Template for the local installation available at
## /opt/gitlab/etc/gitlab.rb.template
# URL on which GitLab will be reachable.
# During installation/upgrades, the value of the environment variable 'EXTERNAL_URL' will be used to populate/replace
# this value.
external_url '{{ external_url }}'
# LetsEncrypt integration
letsencrypt['enable'] = {{ letsencrypt_enabled | bool |lower }}

View File

@@ -0,0 +1,10 @@
---
external_url_hostname: "{{ external_url | ansible.builtin.urlsplit('hostname') }}"
external_url_scheme: "{{ external_url | ansible.builtin.urlsplit('scheme') }}"
supported_external_url_schemes:
- http
- https
supported_install_methods:
- omnibus

View File

@@ -0,0 +1,10 @@
---
plugin: aws_ec2
regions:
- eu-east-2
keyed_groups:
- key: tags.Name
prefix: tag_Name_
separator: ""
hostnames:
- instance-id

View File

@@ -0,0 +1,178 @@
import * as acme from '@pulumiverse/acme';
import * as aws from "@pulumi/aws";
import * as cloudinit from "@pulumi/cloudinit";
import * as command from "@pulumi/command"
import * as fs from 'fs';
import * as pulumi from "@pulumi/pulumi";
import * as tls from "@pulumi/tls";
import * as yaml from "yaml";
import * as time from "@pulumiverse/time";
/**
* Requirements - start
* -------------------------------------
**/
const ami = aws.ec2.getAmiOutput({
owners: [ "amazon" ],
nameRegex: "^al2023-ami-minimal-*",
filters: [{
name: "architecture",
values: [
"arm64",
"x86_64",
],
}],
mostRecent: true,
});
const role = aws.iam.getRoleOutput({
name: "gitlab-omnibus",
});
const subnet = aws.ec2.getSubnetOutput({
filters: [{
name: "tag:Name",
values: [ "eu-east-2a-private" ]
}],
});
/* Requirements - end */
/**
* LetsEncrypt certificate - start
* -------------------------------------
* Leverage the DNS challenge to keep the instance private at all times.
**/
const privateKey = new tls.PrivateKey(
"privateKey",
{ algorithm: "RSA" },
);
const registration = new acme.Registration(
"registration",
{
accountKeyPem: privateKey.privateKeyPem,
emailAddress: "example@company.com",
},
);
const certificate = new acme.Certificate(
"certificate",
{
accountKeyPem: registration.accountKeyPem,
commonName: "gitlab.company.com",
dnsChallenges: [{
provider: "route53",
}],
},
);
/* LetsEncrypt certificate - end */
/**
* Instance - start
* -------------------------------------
**/
const userData = new cloudinit.Config(
"cloudConfig",
{
gzip: true,
base64Encode: true,
parts: [
{
contentType: "text/cloud-config",
content: fs.readFileSync("../../cloud-init/aws.ssm.yaml", "utf8"),
filename: "cloud-config.ssm.yml",
},
{
contentType: "text/cloud-config",
content: pulumi.all([
certificate.certificateDomain.apply(v => v),
certificate.certificatePem.apply(v => v),
certificate.privateKeyPem.apply(v => v),
]).apply(([domain, certificate, privateKey]) => yaml.stringify({
write_files: [
{
path: `/etc/gitlab/ssl/${domain}.crt`,
content: btoa(certificate),
permissions: "0o600",
encoding: "base64",
defer: true,
},
{
path: `/etc/gitlab/ssl/${domain}.key`,
content: btoa(privateKey),
permissions: "0o600",
encoding: "base64",
defer: true,
},
],
})),
filename: "cloud-config.letsencrypt.certificate.yml",
mergeType: "dict(recurse_array,no_replace)+list(append)",
},
],
},
);
const keyPair = new aws.ec2.KeyPair(
"keypair",
{
keyName: "gitlab-omnibus",
publicKey: "ssh-ed25519 AAAAC3NzaC1lZBI1NTE5AAAAIA1CBRl1FnUu/-rUC4NTKo-d99M3bfmJHWckGbYmtYui",
},
);
const instance = new aws.ec2.Instance(
"instance",
{
availabilityZone: subnet.apply(s => s.availabilityZone),
subnetId: subnet.apply(s => s.id),
associatePublicIpAddress: false,
instanceType: "t4g.xlarge",
ami: ami.apply(ami => ami.id),
iamInstanceProfile: role.name,
disableApiTermination: true,
monitoring: true,
userData: userData.rendered,
ebsOptimized: true,
keyName: keyPair.keyName,
rootBlockDevice: {
volumeType: "gp3",
volumeSize: 100,
tags: {
Description: "Instance root disk",
Name: "Gitlab Omnibus",
},
},
tags: {
Name: "Gitlab Omnibus",
SSMManaged: "true",
},
},
);
const wait5Minutes = new time.Sleep(
"wait5Minutes",
{ createDuration: "30s" },
{ dependsOn: [instance] }
);
new command.local.Command(
"ansiblePlaybook",
{ create: "make run" },
{
dependsOn: [
instance,
wait5Minutes,
],
},
);
/* Instance - end */

View File

@@ -0,0 +1,18 @@
{
"name": "gitlab-omnibus-on-aws-ec2",
"main": "index.ts",
"devDependencies": {
"@types/node": "^18"
},
"dependencies": {
"@pulumi/aws": "^6.32.0",
"@pulumi/cloudinit": "^1.4.1",
"@pulumi/command": "^0.10.0",
"@pulumi/pulumi": "^3.114.0",
"@pulumi/tls": "^5.0.2",
"@pulumiverse/acme": "^0.0.1",
"@pulumiverse/time": "^0.0.17",
"typescript": "^5.4.5",
"yaml": "^2.4.2"
}
}

View File

@@ -0,0 +1,3 @@
ansible
ansible-lint
dnspython

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2020",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}

View File

@@ -604,7 +604,7 @@ Solution: give that user _developer_ access or have somebody else with enough pr
[global settings]: https://docs.gitlab.com/charts/charts/globals.html
[how to restart gitlab]: https://docs.gitlab.com/ee/administration/restart_gitlab.html
[install self-managed gitlab]: https://about.gitlab.com/install
[merge request approval rules]: https://gitlab.ops.apolloagriculture.com/help/user/project/merge_requests/approvals/rules
[merge request approval rules]: https://docs.gitlab.com/ee/user/project/merge_requests/approvals/rules.html
[minimal minikube example values file]: https://gitlab.com/gitlab-org/charts/gitlab/-/blob/master/examples/values-minikube-minimum.yaml
[omnibus configuration template]: https://gitlab.com/gitlab-org/omnibus-gitlab/-/raw/master/files/gitlab-config-template/gitlab.rb.template
[operator code]: https://gitlab.com/gitlab-org/cloud-native/gitlab-operator