-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
Co-authored-by: Ilya Egorov <[email protected]>
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/helm/ @Mobyman | ||
/.github/ @Mobyman |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
name: Deploy to Kubernetes | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
- canary | ||
- staging | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
env: | ||
APP_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || github.ref == 'refs/heads/canary' && 'canary' || github.ref == 'refs/heads/staging' && 'staging' || 'unknown' }} | ||
APP_DOMAIN: ${{ github.ref == 'refs/heads/staging' && vars.APP_DOMAIN_STAGING || vars.APP_DOMAIN }} | ||
|
||
permissions: | ||
packages: write | ||
contents: read | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v4 | ||
|
||
- name: Configure AWS Credentials | ||
uses: aws-actions/configure-aws-credentials@v4 | ||
with: | ||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
aws-region: ${{ vars.AWS_REGION }} | ||
|
||
- name: Log in to GitHub Container Registry | ||
uses: docker/login-action@v3 | ||
with: | ||
registry: ghcr.io | ||
username: ${{ github.actor }} | ||
password: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
- name: Set sha-short | ||
run: echo "GITHUB_SHA_SHORT=$(echo $GITHUB_SHA | cut -c 1-7)" >> $GITHUB_ENV | ||
|
||
- id: lower-repo | ||
name: Repository to lowercase | ||
run: | | ||
echo "repository=${GITHUB_REPOSITORY@L}" >> $GITHUB_OUTPUT | ||
- name: Extract metadata (tags, labels) for Docker | ||
id: meta | ||
uses: docker/metadata-action@v5 | ||
with: | ||
images: ghcr.io/${{ steps.lower-repo.outputs.repository }} | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
tags: | | ||
type=sha | ||
type=sha,format=long | ||
type=ref,event=branch | ||
- name: Build and push Docker image ${{ steps.lower-repo.outputs.repository }}:${{ env.APP_ENV }} | ||
uses: docker/build-push-action@v6 | ||
with: | ||
context: . | ||
push: true | ||
tags: ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ env.GITHUB_SHA_SHORT }},ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ env.APP_ENV }} | ||
build-args: | | ||
sha=${{ github.sha }} | ||
sha_short=${{ env.GITHUB_SHA_SHORT }} | ||
app_env=${{ vars.APP_ENV }} | ||
- name: Apply AWS k8s config | ||
run: aws eks update-kubeconfig --name ${{ vars.AWS_CLUSTER }} --region ${{ vars.AWS_REGION }} | ||
|
||
- name: Create namespace | ||
run: | | ||
kubectl create ns ${{ vars.APP_NAME }}-${{ env.APP_ENV }} || echo "Namespace $EKS_NAMESPACE already exists" | ||
- name: Deploy ${{ vars.APP_NAME }} to Kubernetes | ||
run: | | ||
helm upgrade --install ${{ vars.APP_NAME }} ./helm/app \ | ||
--namespace ${{ vars.APP_NAME }}-${{ env.APP_ENV }} \ | ||
--values ./helm/app/values.yaml \ | ||
--values ./helm/app/values-${{ env.APP_ENV }}.yaml \ | ||
--set imageRepo="ghcr.io/${{ steps.lower-repo.outputs.repository }}" \ | ||
--set imageTag="${{ env.GITHUB_SHA_SHORT }}" \ | ||
--set host=${{ env.APP_DOMAIN }} \ | ||
--set appName=${{ vars.APP_NAME }} \ | ||
--set ghcrSecret=${{ secrets.GHCR_SECRET }} \ | ||
--set secrets.publicProxyKey=${{ secrets.NEXT_PUBLIC_MIXPANEL_TOKEN }} \ | ||
--set secrets.publicMixPanelToken=${{ secrets.NEXT_PUBLIC_ANALYTICS_ENABLED }} \ | ||
--set secrets.publicProxyKey=${{ secrets.NEXT_PUBLIC_ANALYTICS_ENABLED }} | ||
- name: Verify deployment | ||
run: | | ||
kubectl -n ${{ vars.APP_NAME }}-${{ env.APP_ENV }} rollout status deployment/${{ vars.APP_NAME }}-${{ env.APP_ENV }} | ||
- name: Telegram Notify | ||
uses: appleboy/[email protected] | ||
if: success() && contains('${{ vars.ENABLE_DEPLOY_BOT }}', 1) | ||
with: | ||
to: ${{ secrets.TELEGRAM_DEPLOY_CHAT_ID }} | ||
token: ${{ secrets.TELEGRAM_DEPLOY_TOKEN }} | ||
format: markdown | ||
message: | | ||
🚂 The application from repository [${{ steps.lower-repo.outputs.repository }}](https://github.com/${{ steps.lower-repo.outputs.repository }}) has been successfully deployed by [${{ github.actor }}](https://github.com/users/${{ github.actor }}) on ${{ env.APP_ENV }}. | ||
🏗️ [GitHub Actions Build](https://github.com/${{ steps.lower-repo.outputs.repository }}/actions/runs/${{ github.run_id }}) | ||
🐳 [Image](https://ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ env.GITHUB_SHA_SHORT }} | ||
🔗 [Link](https://${{ env.APP_DOMAIN }}) | ||
- name: Telegram Notify | ||
uses: appleboy/[email protected] | ||
if: failure() | ||
with: | ||
to: ${{ secrets.TELEGRAM_DEPLOY_CHAT_ID }} | ||
token: ${{ secrets.TELEGRAM_DEPLOY_TOKEN }} | ||
format: markdown | ||
message: | | ||
🚨Deploy of the application from repository [${{ steps.lower-repo.outputs.repository }}](https://github.com/${{ steps.lower-repo.outputs.repository }}) on ${{ env.APP_ENV }} has been failed. | ||
🏗️ [GitHub Actions Build](https://github.com/${{ steps.lower-repo.outputs.repository }}/actions/runs/${{ github.run_id }}) | ||
🐳 [Image](https://ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ env.GITHUB_SHA_SHORT }} | ||
🔗 [Link](https://${{ env.APP_DOMAIN }}) | ||
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
name: Rollback Kubernetes Deployment | ||
|
||
on: | ||
workflow_dispatch: | ||
inputs: | ||
app_env: | ||
description: "Select the environment" | ||
required: true | ||
default: production | ||
type: choice | ||
options: | ||
- production | ||
- staging | ||
revision: | ||
description: "Choose the Helm revision to rollback to" | ||
required: false | ||
|
||
jobs: | ||
rollback: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Configure AWS Credentials | ||
uses: aws-actions/configure-aws-credentials@v4 | ||
with: | ||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
aws-region: ${{ secrets.AWS_REGION }} | ||
|
||
- name: Apply AWS k8s config | ||
run: aws eks update-kubeconfig --name ${{ vars.AWS_CLUSTER }} --region ${{ vars.AWS_REGION }} | ||
|
||
- name: Fetch Helm history | ||
run: | | ||
helm history ${{ vars.APP_NAME }} --namespace ${{ vars.APP_NAME }}-${{ github.event.inputs.app_env }} | ||
- name: Get revision number | ||
if: github.event.inputs.revision == '' | ||
run: | | ||
echo "No revision provided. Exiting." | ||
exit 1 | ||
- name: Perform Helm rollback | ||
run: | | ||
helm rollback ${{ vars.APP_NAME }} ${{ github.event.inputs.revision }} --namespace ${{ vars.APP_NAME }}-${{ github.event.inputs.app_env }} ${{ github.event.inputs.revision }} | ||
- name: Verify rollback | ||
run: | | ||
kubectl -n ${{ vars.APP_NAME }}-${{ github.event.inputs.app_env }} rollout status deployment/${{ vars.APP_NAME }}-${{ github.event.inputs.app_env }} | ||
- name: Show Helm history | ||
run: | | ||
helm history ${{ vars.APP_NAME }} --namespace ${{ vars.APP_NAME }}-${{ github.event.inputs.app_env }} | ||
- name: Telegram Notify | ||
uses: appleboy/[email protected] | ||
with: | ||
to: ${{ secrets.TELEGRAM_DEPLOY_CHAT_ID }} | ||
token: ${{ secrets.TELEGRAM_DEPLOY_TOKEN }} | ||
format: markdown | ||
message: | | ||
🔄 The deployment {{ app_env }} has been rolled back by [${{ github.actor }}](https://github.com/${{ github.actor }}) to revision ${{ github.event.inputs.revision }}. | ||
🏗️ [GitHub Actions Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/helm |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
FROM node:21-alpine AS base | ||
|
||
WORKDIR /app | ||
COPY package*.json ./ | ||
RUN npm install | ||
COPY . . | ||
RUN npm run build | ||
|
||
EXPOSE 3000 | ||
CMD ["npm", "start"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
apiVersion: v2 | ||
name: Node.js Chart | ||
description: A Helm chart for deploying my Node.js application | ||
version: 0.1.2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
metadata: | ||
name: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
namespace: {{ .Release.Namespace }} | ||
labels: | ||
app: {{ .Values.appName }}-{{ .Values.deployEnv }} | ||
release: prometheus-stack | ||
spec: | ||
replicas: {{ .Values.defaultReplicaCount }} | ||
strategy: | ||
type: RollingUpdate | ||
selector: | ||
matchLabels: | ||
app: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
template: | ||
metadata: | ||
labels: | ||
app: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
release: prometheus-stack | ||
spec: | ||
topologySpreadConstraints: | ||
- maxSkew: 1 | ||
topologyKey: kubernetes.io/hostname | ||
whenUnsatisfiable: ScheduleAnyway | ||
labelSelector: | ||
matchLabels: | ||
app: {{ .Values.appName }}-{{ .Values.deployEnv }} | ||
matchLabelKeys: | ||
- pod-template-hash | ||
containers: | ||
- name: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
image: "{{ .Values.imageRepo }}:{{ .Values.imageTag }}" | ||
env: | ||
- name: APP_ENV | ||
value: {{ .Values.deployEnv }} | ||
- name: APP_VERSION | ||
value: {{ .Values.appVersion | quote }} | ||
- name: NEXT_PUBLIC_PROXY_KEY | ||
value: {{ .Values.secrets.publicProxyKey | quote }} | ||
- name: NEXT_PUBLIC_MIXPANEL_TOKEN | ||
value: {{ .Values.secrets.publicMixPanelToken | quote }} | ||
- name: NEXT_PUBLIC_ANALYTICS_ENABLED | ||
value: {{ .Values.secrets.publicProxyKey | quote }} | ||
ports: | ||
- containerPort: {{ .Values.containerPort }} | ||
resources: | ||
limits: | ||
cpu: {{ .Values.cpuLimit }} | ||
memory: {{ .Values.memoryLimit }} | ||
requests: | ||
cpu: {{ .Values.cpuRequest }} | ||
memory: {{ .Values.memoryRequest }} | ||
imagePullPolicy: Always | ||
imagePullSecrets: | ||
- name: dockerconfigjson-github-com |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
kind: Secret | ||
type: kubernetes.io/dockerconfigjson | ||
apiVersion: v1 | ||
metadata: | ||
name: dockerconfigjson-github-com | ||
namespace: {{ .Release.Namespace }} | ||
labels: | ||
app: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
data: | ||
.dockerconfigjson: {{ .Values.ghcrSecret }} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
apiVersion: autoscaling/v2 | ||
kind: HorizontalPodAutoscaler | ||
metadata: | ||
name: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
namespace: {{ .Release.Namespace }} | ||
spec: | ||
scaleTargetRef: | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
name: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
minReplicas: {{ .Values.minReplicas }} | ||
maxReplicas: {{ .Values.maxReplicas }} | ||
metrics: | ||
- type: Resource | ||
resource: | ||
name: cpu | ||
target: | ||
type: Utilization | ||
averageUtilization: 70 | ||
- type: Resource | ||
resource: | ||
name: memory | ||
target: | ||
type: Utilization | ||
averageUtilization: 80 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{{ if .Values.publicService }} | ||
apiVersion: networking.k8s.io/v1 | ||
kind: Ingress | ||
metadata: | ||
name: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
namespace: {{ .Release.Namespace }} | ||
annotations: | ||
kubernetes.io/ingress.class: "nginx" | ||
nginx.ingress.kubernetes.io/ssl-redirect: "{{ .Values.sslRedirect }}" | ||
nginx.ingress.kubernetes.io/proxy-connect-timeout: "10s" | ||
nginx.ingress.kubernetes.io/proxy-read-timeout: "15s" | ||
nginx.ingress.kubernetes.io/proxy-send-timeout: "15s" | ||
nginx.ingress.kubernetes.io/from-to-www-redirect: "true" | ||
nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout http_502 http_503 http_504" | ||
nginx.ingress.kubernetes.io/proxy-next-upstream-tries: "3" | ||
cert-manager.io/cluster-issuer: {{ .Values.tlsIssuer }} | ||
{{- if eq .Values.deployEnv "canary" }} | ||
nginx.ingress.kubernetes.io/canary: "true" | ||
nginx.ingress.kubernetes.io/canary-by-cookie: {{ .Values.canaryCookie | quote }} | ||
nginx.ingress.kubernetes.io/canary-weight: {{ .Values.canaryWeight | quote }} | ||
{{- end }} | ||
nginx.ingress.kubernetes.io/server-snippet: | | ||
location ~ ^/(metrics|ready|health)$ { | ||
return 403; | ||
} | ||
labels: | ||
release: prometheus-stack | ||
app: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
spec: | ||
tls: | ||
- hosts: | ||
- {{ .Values.host }} | ||
secretName: {{ .Values.host }} | ||
rules: | ||
- host: {{ .Values.host }} | ||
http: | ||
paths: | ||
- path: / | ||
pathType: Prefix | ||
backend: | ||
service: | ||
name: {{ .Values.appName }}-{{ .Values.deployEnv }} | ||
port: | ||
name: http | ||
{{- end }} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
apiVersion: v1 | ||
kind: ResourceQuota | ||
metadata: | ||
name: resource-quota | ||
namespace: {{ .Release.Namespace }} | ||
spec: | ||
hard: | ||
{{- if eq .Values.deployEnv "staging" }} | ||
pods: "2" | ||
requests.memory: "256Mi" | ||
limits.cpu: "1" | ||
limits.memory: "16Gi" | ||
persistentvolumeclaims: "0" | ||
{{- end }} | ||
{{- if eq .Values.deployEnv "canary" }} | ||
pods: "10" | ||
limits.cpu: "4" | ||
limits.memory: "8Gi" | ||
persistentvolumeclaims: "0" | ||
{{- end }} | ||
{{- if eq .Values.deployEnv "production" }} | ||
pods: "100" | ||
limits.cpu: "8" | ||
limits.memory: "16Gi" | ||
persistentvolumeclaims: "0" | ||
{{- end }} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
apiVersion: monitoring.coreos.com/v1 | ||
kind: ServiceMonitor | ||
metadata: | ||
name: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
namespace: {{ .Release.Namespace }} | ||
labels: | ||
release: prometheus-stack | ||
spec: | ||
selector: | ||
matchLabels: | ||
app: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
endpoints: | ||
- port: http | ||
interval: 30s | ||
path: /metrics | ||
- port: https | ||
interval: 30s | ||
path: /metrics | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
apiVersion: v1 | ||
kind: Service | ||
metadata: | ||
name: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
namespace: {{ .Release.Namespace }} | ||
labels: | ||
app: {{ .Values.appName }}-{{ .Values.deployEnv }} | ||
release: prometheus-stack | ||
spec: | ||
type: ClusterIP | ||
selector: | ||
app: {{ .Values.appName }}-{{ .Values.deployEnv}} | ||
ports: | ||
- name: http | ||
protocol: TCP | ||
port: 80 | ||
targetPort: {{ .Values.containerPort }} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
deployEnv: canary | ||
replicaCount: 2 | ||
imageTag: "canary" | ||
canaryWeight: 10 | ||
|
||
minReplicas: 1 | ||
maxReplicas: 1 | ||
|
||
memoryLimit: 120Mi | ||
memoryRequest: 100Mi |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
deployEnv: production | ||
defaultReplicaCount: 2 | ||
imageTag: "production" | ||
|
||
minReplicas: 2 | ||
maxReplicas: 4 | ||
|
||
memoryLimit: 120Mi | ||
memoryRequest: 100Mi |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
deployEnv: staging | ||
replicaCount: 1 | ||
imageTag: "staging" | ||
|
||
minReplicas: 1 | ||
maxReplicas: 1 | ||
|
||
memoryLimit: 120Mi | ||
memoryRequest: 100Mi |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# app | ||
appVersion: "0.1" | ||
|
||
# limits & requests | ||
cpuLimit: "500m" | ||
memoryLimit: "128Mi" | ||
cpuRequest: "500m" | ||
memoryRequest: "64Mi" | ||
|
||
# replicas | ||
minReplicas: 2 | ||
maxReplicas: 40 | ||
|
||
# docker | ||
containerPort: 3000 | ||
nodePort: 80 | ||
|
||
# from github deploy | ||
imageRepo: "" | ||
imageTag: "" | ||
host: "" | ||
appName: "" | ||
ghcrSecret: "" | ||
|
||
tlsCert: "" | ||
tlsKey: "" | ||
|
||
canaryCookie: "canary_tPIzU7rz5ecBWK2gFOs72o5s2qr0kz" | ||
|
||
# do not change | ||
tlsIssuer: "letsencrypt" | ||
certIssuingMode: false | ||
|
||
# http | ||
publicService: true | ||
sslRedirect: false | ||
|
||
|
||
secrets: | ||
publicProxyKey: "" | ||
publicMixPanelToken: "" | ||
secrets.publicProxyKey: "" |
Large diffs are not rendered by default.