diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..87449a9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:1.22 + +RUN curl -L https://github.com/golang-migrate/migrate/releases/download/v4.15.0/migrate.linux-amd64.tar.gz | tar xvz -C /usr/local/bin + +WORKDIR /usr/src/app + +COPY go.mod go.sum ./ + +RUN go mod download && go mod verify + +COPY . . + +RUN go build -v -o /usr/local/bin/app ./cmd/... + +CMD ["app"] diff --git a/Makefile b/Makefile index 92e2dc7..e6b136a 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,15 @@ migrate-up: migrate-down: migrate -database $(DB_URL) -path internal/database/postgres/migration down +migrate-docker-down: + docker-compose run app migrate -path ./internal/database/postgres/migration -database "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable" down + +migrate-docker-up: + docker-compose run app migrate -path ./internal/database/postgres/migration -database "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable" up + +kube-forward-port: + kubectl port-forward services/booking-app-app 8080:8080 + migrate-up-test: migrate -database $(DB_URL_TEST) -path internal/database/postgres/migration up diff --git a/README.md b/README.md index deb00a0..25a9bc9 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,17 @@ After initializing all the necessary dependencies, you can run project: go run cmd/main.go ``` +Use, if running on local machine: +``` +POSTGRES_HOST=localhost +REDIS_HOST=localhost +``` +if running on docker or kubernetes: +``` +POSTGRES_HOST=postgres +REDIS_HOST=redis +``` + ### Testing to test integration tests: @@ -59,4 +70,26 @@ also set config variables as below example: ``` make export_env source set_env.sh -``` \ No newline at end of file +``` + +### Kubernetes + +[Install minikube if you dont have it yet](https://minikube.sigs.k8s.io/docs/start/) + +Then you just need too, apply kubernetes configuration to run the project: + +``` +helm install booking-app ./booking-chart +``` + +or to update: +``` +helm upgrade booking-app ./booking-chart +``` + +Warning: job 'migrate-job' may crash before 'app' pod is running, that is why you may recreate 'migrate-job' to run migration on postgres schema after 'app' pod is ready. + +To forward a local port 8080 to a port on a Kubernetes service: +``` +make kube-forward-port +``` diff --git a/booking-chart/.helmignore b/booking-chart/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/booking-chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/booking-chart/Chart.yaml b/booking-chart/Chart.yaml new file mode 100644 index 0000000..fe10b94 --- /dev/null +++ b/booking-chart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: booking-chart +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/booking-chart/templates/app_deployment.yaml b/booking-chart/templates/app_deployment.yaml new file mode 100644 index 0000000..c96c37b --- /dev/null +++ b/booking-chart/templates/app_deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-app + labels: + app: {{ .Release.Name }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ .Release.Name }} + spec: + containers: + - name: {{ .Release.Name }}-app + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + ports: + - containerPort: {{ .Values.service.port }} + env: + {{- range $key, $value := .Values.env }} + - name: {{ $key }} + value: "{{ $value }}" + {{- end }} + resources: {} + restartPolicy: Always diff --git a/booking-chart/templates/app_service.yaml b/booking-chart/templates/app_service.yaml new file mode 100644 index 0000000..b1a2840 --- /dev/null +++ b/booking-chart/templates/app_service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-app + labels: + app: {{ .Release.Name }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.port }} + selector: + app: {{ .Release.Name }} diff --git a/booking-chart/templates/configmap_env.yaml b/booking-chart/templates/configmap_env.yaml new file mode 100644 index 0000000..eb867ba --- /dev/null +++ b/booking-chart/templates/configmap_env.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-local-env + labels: + io.kompose.service: app-local-env +data: + ACCESS_TOKEN_EXPIRE: "{{ .Values.env.ACCESS_TOKEN_EXPIRE }}" + ADDRESS: "{{ .Values.env.ADDRESS }}" + ENVIRONMENT: "{{ .Values.env.ENVIRONMENT }}" + HEADER_TIMEOUT: "{{ .Values.env.HEADER_TIMEOUT }}" + JWT_TOKEN_SECRET: "{{ .Values.env.JWT_TOKEN_SECRET }}" + POSTGRES_DB: "{{ .Values.env.POSTGRES_DB }}" + POSTGRES_HOST: "{{ .Values.env.POSTGRES_HOST }}" + POSTGRES_MAX_CONNECTIONS: "{{ .Values.env.POSTGRES_MAX_CONNECTIONS }}" + POSTGRES_PORT: "{{ .Values.env.POSTGRES_PORT }}" + POSTGRES_TIMEOUT: "{{ .Values.env.POSTGRES_TIMEOUT }}" + POSTGRES_USER: "{{ .Values.env.POSTGRES_USER }}" + REDIS_DATABASE: "{{ .Values.env.REDIS_DATABASE }}" + REDIS_HOST: "{{ .Values.env.REDIS_HOST }}" + REDIS_POOL_SIZE: "{{ .Values.env.REDIS_POOL_SIZE }}" + REDIS_PORT: "{{ .Values.env.REDIS_PORT }}" + REDIS_TIMEOUT: "{{ .Values.env.REDIS_TIMEOUT }}" + REDIS_TTL: "{{ .Values.env.REDIS_TTL }}" + REST_PORT: "{{ .Values.env.REST_PORT }}" diff --git a/booking-chart/templates/migrate_job.yaml b/booking-chart/templates/migrate_job.yaml new file mode 100644 index 0000000..8620003 --- /dev/null +++ b/booking-chart/templates/migrate_job.yaml @@ -0,0 +1,26 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-migrate-job +spec: + template: + spec: + containers: + - name: migrate + image: {{ .Values.migrate.image }}:{{ .Values.migrate.tag }} + command: + - sh + - -c + - migrate -path ./internal/database/postgres/migration -database postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@{{ .Values.env.POSTGRES_HOST}}:{{ .Values.env.POSTGRES_PORT }}/$(POSTGRES_DB)?sslmode=disable up + env: + - name: POSTGRES_USER + value: {{ .Values.env.POSTGRES_USER }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-postgres-secret + key: POSTGRES_PASSWORD + - name: POSTGRES_DB + value: {{ .Values.env.POSTGRES_DB }} + restartPolicy: Never + backoffLimit: {{ .Values.migrate.backoffLimit }} diff --git a/booking-chart/templates/postgres_deployment.yaml b/booking-chart/templates/postgres_deployment.yaml new file mode 100644 index 0000000..abbd7fb --- /dev/null +++ b/booking-chart/templates/postgres_deployment.yaml @@ -0,0 +1,48 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-postgres + labels: + app: {{ .Release.Name }}-postgres + annotations: + kompose.cmd: kompose convert -f docker-compose.yml -o kubermanifests.yaml + kompose.version: 1.34.0 (cbf2835db) +spec: + replicas: {{ .Values.postgres.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }}-postgres + strategy: + type: Recreate + template: + metadata: + labels: + app: {{ .Release.Name }}-postgres + annotations: + kompose.cmd: kompose convert -f docker-compose.yml -o kubermanifests.yaml + kompose.version: 1.34.0 (cbf2835db) + spec: + containers: + - name: {{ .Release.Name }}-postgres + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + ports: + - containerPort: {{ .Values.env.POSTGRES_PORT }} + protocol: TCP + env: + - name: POSTGRES_DB + value: "{{ .Values.env.POSTGRES_DB }}" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-postgres-secret + key: POSTGRES_PASSWORD + - name: POSTGRES_USER + value: "{{ .Values.env.POSTGRES_USER }}" + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: postgres-data + restartPolicy: Always + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-postgres-data diff --git a/booking-chart/templates/postgres_pvc.yaml b/booking-chart/templates/postgres_pvc.yaml new file mode 100644 index 0000000..9433dd7 --- /dev/null +++ b/booking-chart/templates/postgres_pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-postgres-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/booking-chart/templates/postgres_secret.yaml b/booking-chart/templates/postgres_secret.yaml new file mode 100644 index 0000000..1eba5ec --- /dev/null +++ b/booking-chart/templates/postgres_secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-postgres-secret +type: Opaque +data: + POSTGRES_PASSWORD: {{ .Values.env.POSTGRES_PASSWORD | b64enc }} diff --git a/booking-chart/templates/postgres_service.yaml b/booking-chart/templates/postgres_service.yaml new file mode 100644 index 0000000..1891afe --- /dev/null +++ b/booking-chart/templates/postgres_service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-postgres + labels: + app: {{ .Release.Name }}-postgres + annotations: + kompose.cmd: kompose convert -f docker-compose.yml -o kubermanifests.yaml + kompose.version: 1.34.0 (cbf2835db) +spec: + ports: + - name: "5432" + port: {{ .Values.env.POSTGRES_PORT }} + targetPort: {{ .Values.env.POSTGRES_PORT }} + selector: + app: {{ .Release.Name }}-postgres diff --git a/booking-chart/templates/redis_deployment.yaml b/booking-chart/templates/redis_deployment.yaml new file mode 100644 index 0000000..181a7ce --- /dev/null +++ b/booking-chart/templates/redis_deployment.yaml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-redis + labels: + app: {{ .Release.Name }}-redis + annotations: + kompose.cmd: kompose convert -f docker-compose.yml -o kubermanifests.yaml + kompose.version: 1.34.0 (cbf2835db) +spec: + replicas: {{ .Values.redis.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }}-redis + strategy: + type: Recreate + template: + metadata: + labels: + app: {{ .Release.Name }}-redis + spec: + containers: + - name: redis + image: {{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }} + args: + - redis-server + - --requirepass + - "$(REDIS_PASSWORD)" + ports: + - containerPort: 6379 + protocol: TCP + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-redis-secret + key: REDIS_PASSWORD + volumeMounts: + - mountPath: /data + name: redis-data + restartPolicy: Always + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-redis-data diff --git a/booking-chart/templates/redis_pvc.yaml b/booking-chart/templates/redis_pvc.yaml new file mode 100644 index 0000000..8e335b7 --- /dev/null +++ b/booking-chart/templates/redis_pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-redis-data + labels: + app: {{ .Release.Name }}-redis-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.redis.persistentVolume.storage }} diff --git a/booking-chart/templates/redis_secret.yaml b/booking-chart/templates/redis_secret.yaml new file mode 100644 index 0000000..225d513 --- /dev/null +++ b/booking-chart/templates/redis_secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-redis-secret + labels: + app: {{ .Release.Name }}-redis-secret +type: Opaque +data: + REDIS_PASSWORD: {{ .Values.env.REDIS_PASSWORD | b64enc }} diff --git a/booking-chart/templates/redis_service.yaml b/booking-chart/templates/redis_service.yaml new file mode 100644 index 0000000..ed1a70e --- /dev/null +++ b/booking-chart/templates/redis_service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-redis + labels: + app: {{ .Release.Name }}-redis + annotations: + kompose.cmd: kompose convert -f docker-compose.yml -o kubermanifests.yaml + kompose.version: 1.34.0 (cbf2835db) +spec: + ports: + - name: redis + port: {{ .Values.env.REDIS_PORT }} + targetPort: {{ .Values.env.REDIS_PORT }} + selector: + app: {{ .Release.Name }}-redis diff --git a/booking-chart/values.yaml b/booking-chart/values.yaml new file mode 100644 index 0000000..f7a44bf --- /dev/null +++ b/booking-chart/values.yaml @@ -0,0 +1,152 @@ +# Default values for booking-chart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: dilyara4949/flight-book + tag: latest + pullPolicy: IfNotPresent + +service: + type: NodePort + port: 8080 + +env: + ACCESS_TOKEN_EXPIRE: "877" + ADDRESS: "0.0.0.0" + ENVIRONMENT: "local" + HEADER_TIMEOUT: "5s" + JWT_TOKEN_SECRET: "my_secret_key" + REST_PORT: "8080" + POSTGRES_DB: "postgres" + POSTGRES_USER: "postgres" + db: "postgres" + POSTGRES_HOST: "postgres" + POSTGRES_MAX_CONNECTIONS: "20" + POSTGRES_PORT: "5432" + POSTGRES_TIMEOUT: "30" + POSTGRES_PASSWORD: "12345" + REDIS_PASSWORD: "12345" + REDIS_HOST: "localhost" + REDIS_PORT: "6379" + REDIS_TIMEOUT: "10" + REDIS_TTL: "5" + REDIS_DATABASE: "0" + REDIS_POOL_SIZE: "10" + + +migrate: + image: dilyara4949/flight-book + tag: latest + backoffLimit: 4 + +postgres: + replicaCount: 1 + image: + repository: postgres + tag: latest + +redis: + replicaCount: 1 + image: + repository: redis + tag: latest + persistentVolume: + storage: 100Mi + + +imagePullSecrets: [ ] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: { } + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: { } +podLabels: { } + +podSecurityContext: { } +# fsGroup: 2000 + +securityContext: { } + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true +# runAsUser: 1000 + + + +ingress: + enabled: false + className: "" + annotations: { } + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [ ] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: { } + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m +# memory: 128Mi + +livenessProbe: + httpGet: + path: / + port: http +readinessProbe: + httpGet: + path: / + port: http + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# 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 + +nodeSelector: { } + +tolerations: [ ] + +affinity: { } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..895ba00 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: '3.8' + +services: + app: + build: . + ports: + - "8080:8080" + env_file: + - ./local.env + depends_on: + - postgres + - redis + + postgres: + image: postgres:latest + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "5435:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + redis: + image: redis:latest + command: redis-server --requirepass "${REDIS_PASSWORD}" + ports: + - "6379:6379" + volumes: + - redis_data:/data + +volumes: + postgres_data: + redis_data: \ No newline at end of file