From 73ac23d9714d9738d6c7a511980a59342810f633 Mon Sep 17 00:00:00 2001 From: Igor Rzegocki Date: Thu, 21 Mar 2024 12:40:38 +0100 Subject: [PATCH] add k8s deployment --- .github/workflows/release.yaml | 80 ++++++++++ .gitignore | 4 - Capfile | 4 - Dockerfile | 23 +++ Gemfile | 14 +- Gemfile.lock | 29 ++-- app/controllers/health_check_controller.rb | 12 ++ config/database.example.yml | 25 --- config/database.yml | 14 ++ config/email.example.yml | 50 ------ config/email.yml | 16 ++ config/environments/production.rb | 2 +- config/environments/staging.rb | 3 + config/routes.rb | 1 + config/unicorn.conf.rb | 57 ------- config/unicorn.dev.rb | 2 - k8s/app/Chart.yaml | 23 +++ k8s/app/templates/deployment-application.yaml | 144 ++++++++++++++++++ k8s/app/templates/ingress.yaml | 23 +++ k8s/app/templates/secret-postgresql.yaml | 9 ++ k8s/app/templates/service.yaml | 20 +++ k8s/app/values-production.yaml | 132 ++++++++++++++++ k8s/app/values-staging.yaml | 78 ++++++++++ k8s/application.yaml | 25 +++ script/build-docker.sh | 10 ++ 25 files changed, 643 insertions(+), 157 deletions(-) create mode 100644 .github/workflows/release.yaml delete mode 100644 Capfile create mode 100644 Dockerfile create mode 100644 app/controllers/health_check_controller.rb delete mode 100644 config/database.example.yml create mode 100644 config/database.yml delete mode 100644 config/email.example.yml create mode 100644 config/email.yml create mode 100644 config/environments/staging.rb delete mode 100644 config/unicorn.conf.rb delete mode 100644 config/unicorn.dev.rb create mode 100644 k8s/app/Chart.yaml create mode 100644 k8s/app/templates/deployment-application.yaml create mode 100644 k8s/app/templates/ingress.yaml create mode 100644 k8s/app/templates/secret-postgresql.yaml create mode 100644 k8s/app/templates/service.yaml create mode 100644 k8s/app/values-production.yaml create mode 100644 k8s/app/values-staging.yaml create mode 100644 k8s/application.yaml create mode 100755 script/build-docker.sh diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..bbe7f49 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,80 @@ +# yamllint disable rule:comments +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Release Hermes + +'on': + push: + branches: + - master + - feat/k8s + +env: + SERVICE_NAME: hermes + +jobs: + build: + runs-on: arc-runner-set-prod + + steps: + - name: Install prerequisites + run: >- + sudo apt-get update && + sudo apt-get install --yes --no-install-recommends curl git && + sudo apt-get clean && + sudo rm -rf /var/lib/apt/lists/* + + - name: Generate Token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + id: generate-token + with: + app_id: "${{ secrets.RENOVATEBOT_APP_ID }}" + private_key: "${{ secrets.RENOVATEBOT_APP_PRIVATE_KEY }}" + + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + token: "${{ steps.generate-token.outputs.token }}" + + - name: Get build tag + id: vars + run: |- + echo "${{ secrets.IFAD_BOT_SSH_KEY }}" > /tmp/ssh_key + chmod 600 /tmp/ssh_key + echo "docker_image_tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "service_name=${SERVICE_NAME}" >> $GITHUB_OUTPUT + + - name: Login to IFAD Registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ secrets.HARBOR_URL }} + username: ${{ secrets.HARBOR_USER }} + password: ${{ secrets.HARBOR_PASS }} + + - name: Build and push image + uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + with: + context: . + push: true + tags: ${{ secrets.HARBOR_URL }}/services/${{ steps.vars.outputs.service_name }}:${{ steps.vars.outputs.docker_image_tag }} + secret-files: "ssh_private_key=/tmp/ssh_key" + + - name: Install ArgoCD + run: | + curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64 + chmod +x argocd + export USER=argocd + # The master branch is linked to ArgoCD production, all other branches are linked to ArgoCD staging + if [ "${{ github.ref }}" = "refs/heads/master" ]; then + ./argocd login ${{ secrets.PROD_ARGOCD_URL }} --username ${{ secrets.PROD_ARGOCD_USER }} --password ${{ secrets.PROD_ARGOCD_PASS }} --insecure --skip-test-tls --grpc-web + else + ./argocd login ${{ secrets.ARGOCD_URL }} --username ${{ secrets.ARGOCD_USER }} --password ${{ secrets.ARGOCD_PASS }} --insecure --skip-test-tls --grpc-web + fi + + - name: Configure and Update ArgoCD + run: | + ./argocd app set $SERVICE_NAME --plugin-env DEPLOY_TAG="${{ steps.vars.outputs.docker_image_tag }}" + ./argocd app get $SERVICE_NAME --hard-refresh + sleep 10 + ./argocd app sync $SERVICE_NAME +# yamllint enable rule:comments diff --git a/.gitignore b/.gitignore index 9001078..3509844 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,4 @@ /log /tmp -/config/database.yml -/config/email.yml -/config/initializers/secret_token.rb - /public/assets diff --git a/Capfile b/Capfile deleted file mode 100644 index 6a798eb..0000000 --- a/Capfile +++ /dev/null @@ -1,4 +0,0 @@ -load 'deploy' -# Uncomment if you are using Rails' asset pipeline - # load 'deploy/assets' -load 'config/deploy' # remove this line to skip loading any of the default tasks \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6dc616b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM registry.ifad.org/library/ruby:2.3.8 + +COPY --chown=1000:1000 . /app + +USER root + +ENV GIT_SSH_COMMAND='ssh -i /run/secrets/ssh_private_key -o IdentitiesOnly=yes' +ENV HOME=/app +ENV RAILS_ENV=staging + +RUN --mount=type=secret,uid=1000,gid=1000,id=ssh_private_key \ + apt-get update \ + && apt-get install --yes --no-install-recommends build-essential libmagickwand-dev libpq-dev libyaml-dev \ + && su ruby -m -c "script/build-docker.sh" \ + && apt-get remove --purge --yes build-essential \ + && apt-get autoremove --yes --purge \ + && apt-get clean --yes \ + && rm -rf /var/lib/apt/lists/* + +USER 1000:1000 +EXPOSE 3000 + +CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0", "-p", "3000"] diff --git a/Gemfile b/Gemfile index 0bd7c88..8aa58cc 100644 --- a/Gemfile +++ b/Gemfile @@ -1,30 +1,33 @@ source 'https://rubygems.org' +ruby '2.3.8' + gem 'rails', '~> 4.1.7' gem 'devise' gem 'bcrypt-ruby', '~> 3.0.0' gem 'jquery-rails' gem 'jquery-ui-rails' gem 'turbolinks' -gem 'ranked-model', github: 'mixonic/ranked-model' +gem 'ranked-model', git: 'https://github.com/mixonic/ranked-model', ref: '93f4502b776ae527d42bba9ad29eecf7137c6a76' gem 'jbuilder', '~> 1.2' gem 'uglifier', '>= 1.3.0' gem 'therubyracer', platforms: :ruby gem 'less-rails' -gem 'twitter-bootstrap-rails', :git => 'git://github.com/seyhunak/twitter-bootstrap-rails.git' +gem 'twitter-bootstrap-rails', :git => 'https://github.com/seyhunak/twitter-bootstrap-rails.git', ref: '67f160dd2ff5cc7cd843a17866c3b6bc8e7f2794' gem 'momentjs-rails', '>= 2.8.1' gem 'bootstrap3-datetimepicker-rails', '~> 3.1.3' gem 'sanitize-rails' gem 'bootstrap-wysihtml5-rails' gem 'haml-rails' gem 'font-awesome-rails' -gem 'data-confirm-modal', github: 'ifad/data-confirm-modal' +gem 'data-confirm-modal', git: 'https://github.com/ifad/data-confirm-modal', ref: '81d140fe51e70771821ae0434a32dacf69ca5292' gem 'animate-rails' gem 'autosize-rails' gem 'medium-editor-rails' gem 'zeroclipboard-rails' gem 'airbrake' gem 'cancan' +gem 'puma' group :doc do gem 'sdoc', require: false @@ -63,6 +66,9 @@ group :development do end group :production do - gem 'unicorn' gem 'pg' end + +group :build do + gem 'activerecord-nulldb-adapter' +end diff --git a/Gemfile.lock b/Gemfile.lock index 3caf03b..ccfd152 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,20 +1,23 @@ GIT - remote: git://github.com/ifad/data-confirm-modal.git + remote: https://github.com/ifad/data-confirm-modal revision: 81d140fe51e70771821ae0434a32dacf69ca5292 + ref: 81d140fe51e70771821ae0434a32dacf69ca5292 specs: data-confirm-modal (1.0.1) railties (>= 3.0) GIT - remote: git://github.com/mixonic/ranked-model.git + remote: https://github.com/mixonic/ranked-model revision: 93f4502b776ae527d42bba9ad29eecf7137c6a76 + ref: 93f4502b776ae527d42bba9ad29eecf7137c6a76 specs: ranked-model (0.4.0) activerecord (>= 3.1.12) GIT - remote: git://github.com/seyhunak/twitter-bootstrap-rails.git + remote: https://github.com/seyhunak/twitter-bootstrap-rails.git revision: 67f160dd2ff5cc7cd843a17866c3b6bc8e7f2794 + ref: 67f160dd2ff5cc7cd843a17866c3b6bc8e7f2794 specs: twitter-bootstrap-rails (3.2.0) actionpack (>= 3.1) @@ -45,6 +48,8 @@ GEM activemodel (= 4.1.8) activesupport (= 4.1.8) arel (~> 5.0.0) + activerecord-nulldb-adapter (0.4.0) + activerecord (>= 2.0.0) activesupport (4.1.8) i18n (~> 0.6, >= 0.6.9) json (~> 1.7, >= 1.7.7) @@ -164,7 +169,6 @@ GEM jquery-ui-rails (5.0.2) railties (>= 3.2.16) json (1.8.2) - kgio (2.9.2) launchy (2.4.2) addressable (~> 2.3) less (2.6.0) @@ -191,6 +195,7 @@ GEM multi_json (1.10.1) mysql2 (0.3.17) nenv (0.2.0) + nio4r (2.5.2) nokogiri (1.6.4.1) mini_portile (~> 0.6.0) nokogumbo (1.1.12) @@ -213,6 +218,8 @@ GEM pry (>= 0.9.10, < 0.11.0) pry-rails (0.3.2) pry (>= 0.9.10) + puma (5.6.8) + nio4r (~> 2.0) rack (1.5.2) rack-test (0.6.3) rack (>= 1.0) @@ -231,7 +238,6 @@ GEM activesupport (= 4.1.8) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - raindrops (0.13.0) rake (10.4.2) rb-fsevent (0.9.4) rb-inotify (0.9.5) @@ -309,10 +315,6 @@ GEM uglifier (2.5.3) execjs (>= 0.3.0) json (>= 1.8.0) - unicorn (4.8.3) - kgio (~> 2.6) - rack - raindrops (~> 0.7) vcr (2.9.3) warden (1.2.3) rack (>= 1.0) @@ -328,6 +330,7 @@ PLATFORMS ruby DEPENDENCIES + activerecord-nulldb-adapter airbrake animate-rails autosize-rails @@ -362,6 +365,7 @@ DEPENDENCIES pry pry-nav pry-rails + puma rails (~> 4.1.7) ranked-model! rspec-collection_matchers @@ -375,6 +379,11 @@ DEPENDENCIES turbolinks twitter-bootstrap-rails! uglifier (>= 1.3.0) - unicorn vcr zeroclipboard-rails + +RUBY VERSION + ruby 2.3.8p459 + +BUNDLED WITH + 1.17.3 diff --git a/app/controllers/health_check_controller.rb b/app/controllers/health_check_controller.rb new file mode 100644 index 0000000..63244bc --- /dev/null +++ b/app/controllers/health_check_controller.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class HealthCheckController < ActionController::Base # rubocop:disable Rails/ApplicationController + def index + db_alive = ActiveRecord::Base.connection.active? + + render json: { database: db_alive ? 'OK' : 'DOWN' }, + status: db_alive ? :ok : :service_unavailable + rescue StandardError + render json: { database: 'DOWN' }, status: :service_unavailable + end +end diff --git a/config/database.example.yml b/config/database.example.yml deleted file mode 100644 index 51a4dd4..0000000 --- a/config/database.example.yml +++ /dev/null @@ -1,25 +0,0 @@ -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -development: - adapter: sqlite3 - database: db/development.sqlite3 - pool: 5 - timeout: 5000 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - adapter: sqlite3 - database: db/test.sqlite3 - pool: 5 - timeout: 5000 - -production: - adapter: sqlite3 - database: db/production.sqlite3 - pool: 5 - timeout: 5000 diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..7417b87 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,14 @@ +--- +default: &default + adapter: postgresql + encoding: utf8 + timeout: 5000 + database: "<%= ENV.fetch('DB_DATABASE', 'hermes') %>" + username: "<%= ENV.fetch('DB_USERNAME', 'hermes') %>" + password: "<%= ENV.fetch('DB_PASSWORD', '') %>" + host: "<%= ENV.fetch('DB_HOST', 'localhost') %>" + +development: *default +test: *default +staging: *default +production: *default diff --git a/config/email.example.yml b/config/email.example.yml deleted file mode 100644 index da37a62..0000000 --- a/config/email.example.yml +++ /dev/null @@ -1,50 +0,0 @@ -# Production -# -production: - delivery_method: :smtp - - smtp_settings: - address: localhost - port: 25 - - default_url_options: - protocol: http - host: hermes.ifad.org - - -# Staging -# -staging: - delivery_method: :smtp - - smtp_settings: - address: localhost - port: 25 - - default_url_options: - protocol: http - host: hermes.staging.ifad.org - - -# Development -# -development: - delivery_method: :smtp - - smtp_settings: - address: localhost - port: 25 - - default_url_options: - protocol: http - host: localhost - port: 3000 - -# Test -# -test: - delivery_method: :test - - default_url_options: - protocol: http - host: hermes.example.org diff --git a/config/email.yml b/config/email.yml new file mode 100644 index 0000000..5b89cae --- /dev/null +++ b/config/email.yml @@ -0,0 +1,16 @@ +--- +default: &default + delivery_method: :smtp + + smtp_settings: + address: "<%= ENV.fetch('EMAIL_SMTP_SETTINGS_ADDRESS', 'localhost') %>" + port: <%= ENV.fetch('EMAIL_SMTP_SETTINGS_PORT', 1025) %> + + default_url_options: + protocol: "<%= ENV.fetch('EMAIL_DEFAULT_URL_OPTIONS_PROTOCOL', 'http') %>" + host: "<%= ENV.fetch('EMAIL_DEFAULT_URL_OPTIONS_HOST', 'localhost') %>" + +development: *default +test: *default +staging: *default +production: *default diff --git a/config/environments/production.rb b/config/environments/production.rb index 198a4e5..0a62357 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -49,7 +49,7 @@ # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups. - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + config.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout)) # Use a different cache store in production. # config.cache_store = :mem_cache_store diff --git a/config/environments/staging.rb b/config/environments/staging.rb new file mode 100644 index 0000000..4390d6f --- /dev/null +++ b/config/environments/staging.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +load Rails.root.join('config/environments/production.rb') diff --git a/config/routes.rb b/config/routes.rb index 1906ac5..65f0280 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,7 @@ root :to => 'sites#index' + get '/sys/healthcheck' => 'health_check#index' post '/sites/general_broadcast' => 'sites#general_broadcast', as: :general_broadcast resources :sites, except: :new do diff --git a/config/unicorn.conf.rb b/config/unicorn.conf.rb deleted file mode 100644 index 789dc14..0000000 --- a/config/unicorn.conf.rb +++ /dev/null @@ -1,57 +0,0 @@ -worker_processes 6 -timeout 60 - -# Load rails app into the master before forking workers -# for super-fast worker spawn times -preload_app true - -# Listen on a Unix socket -listen "#{ENV['HOME']}/.unicorn.sock" - -# Working directory -working_directory "#{ENV['HOME']}/current" -ENV['BUNDLE_GEMFILE'] = "#{ENV['PWD']}/Gemfile" - -# Log stdout and stderr in separate files -# -stdout_path 'log/unicorn.stdout.log' -stderr_path 'log/unicorn.stderr.log' - -pid "#{ENV['HOME']}/.unicorn.pid" - -Unicorn::HttpServer::START_CTX[0] = "#{ENV['HOME']}/bin/unicorn" - -before_fork do |server, worker| - ## - # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and - # immediately start loading up a new version of itself (loaded with a new - # version of our app). When this new Unicorn is completely loaded - # it will begin spawning workers. The first worker spawned will check to - # see if an .oldbin pidfile exists. If so, this means we've just booted up - # a new Unicorn and need to tell the old one that it can now die. To do so - # we send it a QUIT. - # - # Using this method we get 0 downtime deploys. - - old_pid = "#{ENV['HOME']}/.unicorn.pid.oldbin" - if File.exists?(old_pid) && server.pid != old_pid - begin - Process.kill('QUIT', File.read(old_pid).to_i) - rescue Errno::ENOENT, Errno::ESRCH - # someone else did our job for us - end - end -end - -after_fork do |server, worker| - ## - # Unicorn master loads the app then forks off workers - because of the way - # Unix forking works, we need to make sure we aren't using any of the parent's - # sockets, e.g. db connection - - ActiveRecord::Base.establish_connection - - # CouchDB and Memcached would go here but their connections are established - # on demand, so the master never opens a socket -end - diff --git a/config/unicorn.dev.rb b/config/unicorn.dev.rb deleted file mode 100644 index 89109bb..0000000 --- a/config/unicorn.dev.rb +++ /dev/null @@ -1,2 +0,0 @@ -timeout 600 -listen 3000 diff --git a/k8s/app/Chart.yaml b/k8s/app/Chart.yaml new file mode 100644 index 0000000..2872624 --- /dev/null +++ b/k8s/app/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: pete +description: IFAD pete +type: application +version: 1.0.0 +appVersion: "1.0.0" +# Not all configuration should be used, only the ones which application depend on +dependencies: + - name: elasticsearch + version: 19.13.15 + repository: https://charts.bitnami.com/bitnami + #- name: mariadb + #version: 15.0.1 + #repository: https://charts.bitnami.com/bitnami + - name: memcached + version: 6.7.1 + repository: https://charts.bitnami.com/bitnami + - name: postgresql + version: 13.2.15 + repository: https://charts.bitnami.com/bitnami + - name: redis + version: 18.6.1 + repository: https://charts.bitnami.com/bitnami diff --git a/k8s/app/templates/deployment-application.yaml b/k8s/app/templates/deployment-application.yaml new file mode 100644 index 0000000..699d6d3 --- /dev/null +++ b/k8s/app/templates/deployment-application.yaml @@ -0,0 +1,144 @@ +{{ $appName := .Values.app.name }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/instance: {{ .Values.app.name }}-application + app.kubernetes.io/name: {{ .Values.app.name }} + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} + name: {{ .Values.app.name }}-application + namespace: {{ .Release.Namespace }} +spec: + replicas: {{ .Values.app.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/instance: {{ .Values.app.name }}-application + app.kubernetes.io/name: {{ .Values.app.name }} + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app.kubernetes.io/instance: {{ .Values.app.name }}-application + app.kubernetes.io/name: {{ .Values.app.name }} + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} + spec: + containers: + - name: {{ .Values.app.name }}-application + command: + - bundle + - exec + - rails + - server + - "-b" + - 0.0.0.0 + - "-p" + - "3000" + env: + {{- if .Values.app.database.enabled }} + - name: DB_DATABASE + value: {{ .Values.app.database.database | quote }} + - name: DB_HOST + value: {{ .Values.app.database.host | default (printf "database.%s.svc.cluster.local" .Release.Namespace) | quote }} + - name: DB_PASSWORD + value: {{ .Values.app.database.password | quote }} + - name: DB_USERNAME + value: {{ .Values.app.database.username | quote }} + {{- end }} + {{- if .Values.redis }} + - name: REDIS_URL + {{- if (and .Values.app.redis .Values.app.redis.url) }} + value: {{ .Values.app.redis.url | quote }} + {{- else }} + value: {{ (printf "redis://:%s@redis-master.%s.svc.cluster.local:6379" .Values.app.redis.password .Release.Namespace) | quote }} + {{- end }} + {{- end }} + {{- if .Values.elasticsearch }} + - name: ELASTICSEARCH_URL + {{- if (and .Values.app.elasticsearch .Values.app.elasticsearch.url) }} + value: {{ .Values.app.elasticsearch.url | quote }} + {{- else }} + value: {{ (printf "http://elastic:%s@elasticsearch.%s.svc.cluster.local:9200" .Values.app.elasticsearch.password .Release.Namespace) | quote }} + {{- end }} + {{- end }} + {{- if .Values.memcached }} + - name: MEMCACHE_SERVERS + {{- if (and .Values.app.memcached .Values.app.memcached.url) }} + value: {{ .Values.app.memcached.url | quote }} + {{- else }} + value: {{ (printf "memcached.%s.svc.cluster.local:11211" .Release.Namespace) | quote }} + {{- end }} + {{- end }} + - name: RAILS_ENV + value: {{ .Values.app.appEnv }} + - name: RAILS_LOG_TO_STDOUT + value: "true" + - name: RAILS_SERVE_STATIC_FILES + value: "true" + - name: SECRET_KEY_BASE + value: {{ .Values.app.secrets.secret_key_base | quote }} + {{- with .Values.app.extraEnv }} + {{- toYaml . | nindent 8 }} + {{- end }} + image: "{{ .Values.app.image.repository }}:{{ .Values.app.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.app.image.pullPolicy }} + livenessProbe: + failureThreshold: 3 + httpGet: + path: /sys/healthcheck + port: http + scheme: HTTP + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /sys/healthcheck + port: http + scheme: HTTP + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 1 + ports: + - containerPort: 3000 + name: http + protocol: TCP + resources: + {{- toYaml .Values.app.resources | nindent 12 }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: true + {{- if (or .Values.volumeMounts .Values.app.volumes) }} + volumeMounts: + {{- with .Values.volumeMounts }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- range $volume := .Values.app.volumes }} + - name: {{ $volume.name }} + mountPath: {{ $volume.mountPath }} + {{- end }} + {{- end }} + restartPolicy: Always + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + {{- if (or .Values.volumes .Values.app.volumes) }} + volumes: + {{- with .Values.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- range $volume := .Values.app.volumes }} + - name: {{ $volume.name }} + persistentVolumeClaim: + claimName: {{ $appName }}-{{ $volume.name }} + {{- end }} + {{- end }} diff --git a/k8s/app/templates/ingress.yaml b/k8s/app/templates/ingress.yaml new file mode 100644 index 0000000..7316686 --- /dev/null +++ b/k8s/app/templates/ingress.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + labels: + app.kubernetes.io/instance: {{ .Values.app.name }} + app.kubernetes.io/name: {{ .Values.app.name }} + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} + name: {{ .Values.app.name }} + namespace: {{ .Release.Namespace }} +spec: + ingressClassName: {{ .Values.ingress.className }} + rules: + - host: {{ .Values.app.host }} + http: + paths: + - backend: + service: + name: {{ .Values.app.name }} + port: + number: 3000 + path: / + pathType: Prefix diff --git a/k8s/app/templates/secret-postgresql.yaml b/k8s/app/templates/secret-postgresql.yaml new file mode 100644 index 0000000..04844a0 --- /dev/null +++ b/k8s/app/templates/secret-postgresql.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: postgresql + namespace: {{ .Release.Namespace }} +type: Opaque +stringData: + postgres-password: "{{ .Values.app.database.password }}" diff --git a/k8s/app/templates/service.yaml b/k8s/app/templates/service.yaml new file mode 100644 index 0000000..565511d --- /dev/null +++ b/k8s/app/templates/service.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.app.name }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/instance: {{ .Values.app.name }} + app.kubernetes.io/name: {{ .Values.app.name }} + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +spec: + type: ClusterIP + ports: + - port: 3000 + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/instance: {{ .Values.app.name }}-application + app.kubernetes.io/name: {{ .Values.app.name }} diff --git a/k8s/app/values-production.yaml b/k8s/app/values-production.yaml new file mode 100644 index 0000000..f80e9bc --- /dev/null +++ b/k8s/app/values-production.yaml @@ -0,0 +1,132 @@ +--- +ingress: + className: nginx + +# Additional volumes on the output Deployment definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +app: + # app environment, used for `RAILS_ENV` + appEnv: production + # name of the application, used for uniquely identify k8s manifests + name: example-app + # numbers of replicas + replicaCount: 2 + # URL of the application + host: k-example-app.ifad.org + storageClassName: longhorn + # list of additional environment variables passed to application + extraEnv: [] + # - name: MY_ENV + # value: my-value + sidekiq: + enabled: true + replicaCount: 1 + resources: + requests: + memory: 256Mi + limits: + memory: 2Gi + + image: + repository: registry.ifad.org/services/example-app + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + + resources: + requests: + memory: 256Mi + limits: + memory: 2Gi + + secrets: + secret_key_base: "" + + # if app is using simple postgresql (most of the times) + database: + enabled: true + database: "" + username: "" + password: "" + + # if app is using mariadb/mysql + #database: + # enabled: true + # database: "" + # username: "" + # password: "" + +### DEPENDENCIES +# +# Not all configuration should be used, only the ones which application depend on +# In general, if not necessary, don't fiddle with these, just leave the ones which are relevant +elasticsearch: + global: + kibanaEnabled: false + security: + enabled: true + existingSecret: elasticsearch + tls: + autoGenerated: true + ingress: + enabled: false + metrics: + enabled: true + serviceMonitor: + enabled: true + master: + masterOnly: false + replicaCount: 1 + data: + replicaCount: 0 + coordinating: + replicaCount: 0 + ingest: + replicaCount: 0 + +mariadb: + fullnameOverride: database + architecture: standalone + auth: + existingSecret: mariadb + database: example-app + metrics: + enabled: true + serviceMonitor: + enabled: true + +memcached: + architecture: standalone + metrics: + enabled: true + serviceMonitor: + enabled: true + +postgresql: + fullnameOverride: database + architecture: standalone + auth: + existingSecret: postgresql + database: postgres + metrics: + enabled: true + serviceMonitor: + enabled: true + +redis: + architecture: standalone + metrics: + enabled: true + serviceMonitor: + enabled: true diff --git a/k8s/app/values-staging.yaml b/k8s/app/values-staging.yaml new file mode 100644 index 0000000..b55448f --- /dev/null +++ b/k8s/app/values-staging.yaml @@ -0,0 +1,78 @@ +--- +ingress: + className: nginx + +# Additional volumes on the output Deployment definition. +volumes: [] + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] + +app: + # app environment, used for `RAILS_ENV` + appEnv: staging + # name of the application, used for uniquely identify k8s manifests + name: hermes + # numbers of replicas + replicaCount: 2 + # URL of the application + host: k-hermes.staging.ifad.org + volumes: [] + # list of additional environment variables passed to application + extraEnv: + - name: EMAIL_SMTP_SETTINGS_ADDRESS + value: mine.ifad.org + - name: EMAIL_SMTP_SETTINGS_PORT + value: "15325" + - name: EMAIL_DEFAULT_URL_OPTIONS_PROTOCOL + value: https + - name: EMAIL_DEFAULT_URL_OPTIONS_HOST + value: open.staging.ifad.org + - name: SENTRY_DSN + value: "" # disabled for now, until k8s gets finalized + # value: "" + + sidekiq: + enabled: true + replicaCount: 1 + resources: + requests: + memory: 256Mi + limits: + memory: 2Gi + + image: + repository: registry.ifad.org/services/hermes + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "" + + resources: + requests: + memory: 256Mi + limits: + memory: 2Gi + + secrets: + secret_key_base: "" + + # if app is using simple postgresql (most of the times) + database: + enabled: true + database: "" + username: "" + password: "" + +### DEPENDENCIES +# +# Not all configuration should be used, only the ones which application depend on +postgresql: + fullnameOverride: database + architecture: standalone + auth: + existingSecret: postgresql + database: postgres + metrics: + enabled: true + serviceMonitor: + enabled: true diff --git a/k8s/application.yaml b/k8s/application.yaml new file mode 100644 index 0000000..143536b --- /dev/null +++ b/k8s/application.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: hermes + namespace: argocd +spec: + project: default + source: + repoURL: 'https://github.com/ifad/hermes.git' + path: k8s/app + targetRevision: feat/k8s # for now + plugin: + name: argocd-vault-plugin-helm + destination: + server: 'https://kubernetes.default.svc' + namespace: hermes + syncPolicy: + syncOptions: + - CreateNamespace=true + managedNamespaceMetadata: + labels: + pod-security.kubernetes.io/enforce: restricted + pod-security.kubernetes.io/audit: restricted + pod-security.kubernetes.io/warn: restricted diff --git a/script/build-docker.sh b/script/build-docker.sh new file mode 100755 index 0000000..99a5099 --- /dev/null +++ b/script/build-docker.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +bundle install --jobs $(nproc) --no-cache +DATABASE_URL=nulldb:/// SECRET_KEY_BASE=dummy bundle exec rake assets:precompile +bundle config set --local without build:development:test +bundle install --jobs $(nproc) --no-cache +bundle clean --force +rm -rf .bundle/cache .cache tmp/cache /tmp/*