diff --git a/Libros/.dockerignore b/Libros/.dockerignore new file mode 100644 index 0000000..9c7ebe6 --- /dev/null +++ b/Libros/.dockerignore @@ -0,0 +1,2 @@ +Dockerfile +Makefile diff --git a/Libros/Dockerfile b/Libros/Dockerfile new file mode 100644 index 0000000..b70f962 --- /dev/null +++ b/Libros/Dockerfile @@ -0,0 +1,49 @@ +# syntax=docker/dockerfile:1 + +FROM python:3.11.4-slim-buster + +#FROM python:3.9.19-slim-bullseye + +# set work directory +WORKDIR /app + +# set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# install system dependencies +RUN apt-get update && apt-get install -y sqlite3 + +# create directory for the app user +RUN mkdir -p /app + +# create the app user +#RUN addgroup --system app && adduser --system --group app + +# create the appropriate directories +ENV APP_HOME=/app +RUN mkdir -p $APP_HOME +RUN mkdir -p $APP_HOME/staticfiles +RUN mkdir -p $APP_HOME/mediafiles +WORKDIR $APP_HOME + +RUN pip install --upgrade pip +COPY ./requirements.txt . +RUN pip install -r /app/requirements.txt + +# copy entrypoint.sh +COPY ./entrypoint.sh . + + +# copy project +COPY . $APP_HOME + +# chown all the files to the app user +#RUN chown -R app:app $APP_HOME + +# change to the app user +#USER app +WORKDIR $APP_HOME/biblioteca + +# run entrypoint.sh +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/Libros/K8S/Makefile b/Libros/K8S/Makefile index bf2df28..c3d53bc 100644 --- a/Libros/K8S/Makefile +++ b/Libros/K8S/Makefile @@ -1,16 +1,30 @@ -export IMG_VERSION = 1.0 +export ARQUITECTURA := $(shell lscpu |grep itectur | tr -d ' '| cut -f2 -d':') +export IMG_VERSION = 1.17 +export IMG_NGINX_VERSION = 1.17 + # limpia todo all: imagen clean install imagen: - cd ../servicios; make + cd ../; make install: -kubectl create -f pv-local-libros.yaml -kubectl create -f libros-prod-persistentvolumeclaim.yaml + -kubectl create -f static-volume-persistentvolumeclaim.yaml + -kubectl create -f env-prod-configmap.yaml -envsubst < libros-deployment.yaml |kubectl create -f - + -envsubst < nginx-deployment.yaml |kubectl create -f - + -kubectl create -f nginx-service.yaml clean: + -envsubst < nginx-deployment.yaml |kubectl delete -f - + -kubectl delete -f nginx-service.yaml -envsubst < libros-deployment.yaml |kubectl delete -f - -kubectl delete -f libros-prod-persistentvolumeclaim.yaml + -kubectl delete -f env-prod-configmap.yaml + -kubectl delete -f static-volume-persistentvolumeclaim.yaml -kubectl delete -f pv-local-libros.yaml + +nginx: + cd ../nginx; make diff --git a/Libros/K8S/env-prod-configmap.yaml b/Libros/K8S/env-prod-configmap.yaml new file mode 100644 index 0000000..e664454 --- /dev/null +++ b/Libros/K8S/env-prod-configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +data: + DEBUG: "0" + DJANGO_ALLOWED_HOSTS: localhost 127.0.0.1 [::1] + SECRET_KEY: change_me +kind: ConfigMap +metadata: + labels: + io.kompose.service: web-env-prod + name: env-prod diff --git a/Libros/K8S/libros-deployment.yaml b/Libros/K8S/libros-deployment.yaml index 068f97b..100fd65 100644 --- a/Libros/K8S/libros-deployment.yaml +++ b/Libros/K8S/libros-deployment.yaml @@ -3,12 +3,10 @@ kind: Service metadata: name: libros spec: - type: NodePort ports: - - name: http + - name: "8000" port: 8000 - nodePort: 30343 - targetPort: libros + targetPort: 8000 selector: app: libros --- @@ -31,29 +29,45 @@ spec: app: libros spec: containers: - #- args: - #- gunicorn - #- --bind - #- 0.0.0.0:5000 - #- libros:create_app() - - name: libros - image: registry.reymota.es/libros:$IMG_VERSION + - args: + - gunicorn + - biblioteca.wsgi:application + - --bind + - 0.0.0.0:8000 + name: libros + image: registry.reymota.es/libros-$ARQUITECTURA:$IMG_VERSION env: - name: VERSION value: "$IMG_VERSION" - name: ENVIRONMENT value: "Producción" + - name: DEBUG + valueFrom: + configMapKeyRef: + key: DEBUG + name: env-prod + - name: DJANGO_ALLOWED_HOSTS + valueFrom: + configMapKeyRef: + key: DJANGO_ALLOWED_HOSTS + name: env-prod + - name: SECRET_KEY + valueFrom: + configMapKeyRef: + key: SECRET_KEY + name: env-prod ports: - containerPort: 8000 - name: libros - resources: {} + protocol: TCP volumeMounts: - - mountPath: /libros/media + - mountPath: /app/biblioteca/mediafiles name: libros-media - - mountPath: /libros/datos + - mountPath: /app/biblioteca/datos name: libros-datos - - mountPath: /libros/gestion/migrations + - mountPath: /app/gestion/migrations name: libros-migrations + - mountPath: /app/biblioteca/staticfiles + name: static-volume imagePullSecrets: - name: myregistrykey restartPolicy: Always @@ -67,4 +81,7 @@ spec: - name: libros-migrations persistentVolumeClaim: claimName: libros-migrations + - name: static-volume + persistentVolumeClaim: + claimName: static-volume status: {} diff --git a/Libros/K8S/nginx-deployment.yaml b/Libros/K8S/nginx-deployment.yaml new file mode 100644 index 0000000..ba52d20 --- /dev/null +++ b/Libros/K8S/nginx-deployment.yaml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.34.0 (cbf2835db) + labels: + io.kompose.service: nginx + name: nginx +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: nginx + strategy: + type: Recreate + template: + metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.34.0 (cbf2835db) + labels: + io.kompose.service: nginx + spec: + containers: + - image: registry.reymota.es/nginx-$ARQUITECTURA:$IMG_NGINX_VERSION + name: nginx + ports: + - containerPort: 80 + protocol: TCP + volumeMounts: + - mountPath: /app/biblioteca/staticfiles + name: static-volume + - mountPath: /app/biblioteca/mediafiles + name: libros-media + imagePullSecrets: + - name: myregistrykey + restartPolicy: Always + volumes: + - name: static-volume + persistentVolumeClaim: + claimName: static-volume + - name: libros-media + persistentVolumeClaim: + claimName: libros-media diff --git a/Libros/K8S/nginx-service.yaml b/Libros/K8S/nginx-service.yaml new file mode 100644 index 0000000..3e15c36 --- /dev/null +++ b/Libros/K8S/nginx-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.34.0 (cbf2835db) + labels: + io.kompose.service: nginx + name: nginx +spec: + type: NodePort + ports: + - name: "1337" + port: 1337 + nodePort: 30343 + targetPort: 80 + selector: + io.kompose.service: nginx + diff --git a/Libros/K8S/pv-local-libros.yaml b/Libros/K8S/pv-local-libros.yaml index 9fb0283..085539c 100644 --- a/Libros/K8S/pv-local-libros.yaml +++ b/Libros/K8S/pv-local-libros.yaml @@ -34,9 +34,22 @@ metadata: app: libros spec: capacity: - storage: 50Mi + storage: 150Mi accessModes: - ReadWriteOnce hostPath: path: "/mnt/Externo/libros/datos" - +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: libros-static-folder + labels: + app: libros +spec: + capacity: + storage: 70Mi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/Externo/libros/static" diff --git a/Libros/K8S/static-volume-persistentvolumeclaim.yaml b/Libros/K8S/static-volume-persistentvolumeclaim.yaml new file mode 100644 index 0000000..555a643 --- /dev/null +++ b/Libros/K8S/static-volume-persistentvolumeclaim.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + io.kompose.service: static-volume + name: static-volume +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 70Mi diff --git a/Libros/K8S/verImg.sh b/Libros/K8S/verImg.sh index ca98495..2c48332 100644 --- a/Libros/K8S/verImg.sh +++ b/Libros/K8S/verImg.sh @@ -1 +1 @@ -docker run -it registry.reymota.es/libros:1.0 bash +docker run -it registry.reymota.es/libros:1.19 bash diff --git a/Libros/Makefile b/Libros/Makefile new file mode 100644 index 0000000..b9af19b --- /dev/null +++ b/Libros/Makefile @@ -0,0 +1,8 @@ +install: + + echo "Creando imagen con version ${IMG_VERSION}" + + docker build --no-cache -t registry.reymota.es/libros-${ARQUITECTURA}:${IMG_VERSION} . + docker push registry.reymota.es/libros-${ARQUITECTURA}:${IMG_VERSION} + + diff --git a/Libros/README.md b/Libros/README.md index 5f1a71c..710f004 100644 --- a/Libros/README.md +++ b/Libros/README.md @@ -9,4 +9,8 @@ User Group: {{ user.groups.all.0 }} Email: {{ user.email }} -Session Started at: {{ user.last_login }} \ No newline at end of file +Session Started at: {{ user.last_login }} + + +## Para funcionar con gunicorn y nginx +https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/ diff --git a/Libros/biblioteca/biblioteca/settings.py b/Libros/biblioteca/biblioteca/settings.py index 1b63f3a..525451c 100644 --- a/Libros/biblioteca/biblioteca/settings.py +++ b/Libros/biblioteca/biblioteca/settings.py @@ -21,13 +21,15 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-q0bxrr=*ab*b8oa#=+-@_4cu-cp6)c3lz#+!c_%51$*!bnz%4!' +#SECRET_KEY = 'django-insecure-q0bxrr=*ab*b8oa#=+-@_4cu-cp6)c3lz#+!c_%51$*!bnz%4!' +SECRET_KEY = os.environ.get("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +#DEBUG = True -ALLOWED_HOSTS = [] +DEBUG = bool(os.environ.get("DEBUG", default=0)) +ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ") # Application definition @@ -82,7 +84,8 @@ DATABASES = { 'NAME': os.path.join(BASE_DIR, 'datos/libros.sqlite3'), } } -print("DB:",DATABASES) +#print("DB:",DATABASES) +#print("BASE_DIR:",BASE_DIR) # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators @@ -118,7 +121,8 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / "staticfiles" # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field @@ -131,4 +135,26 @@ LOGOUT_REDIRECT_URL = 'principal' AUTH_USER_MODEL = "gestion.ReyMotaUser" -MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') # 'media' is my media folder \ No newline at end of file +MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') # 'media' is my media folder + + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'filename': '/tmp/debug.log', + }, + }, + 'loggers': { + 'django': { + 'handlers': ['file'], + 'level': 'DEBUG', + 'propagate': True, + }, + }, +} + +CSRF_TRUSTED_ORIGINS = ["http://localhost:30443"] diff --git a/Libros/biblioteca/datos/libros.sqlite3 b/Libros/biblioteca/datos/libros.sqlite3 index c4fb94b..f27edd6 100644 Binary files a/Libros/biblioteca/datos/libros.sqlite3 and b/Libros/biblioteca/datos/libros.sqlite3 differ diff --git a/Libros/biblioteca/gestion/migrations/0001_initial.py b/Libros/biblioteca/gestion/migrations/0001_initial.py index bf54df8..5819199 100644 --- a/Libros/biblioteca/gestion/migrations/0001_initial.py +++ b/Libros/biblioteca/gestion/migrations/0001_initial.py @@ -1,9 +1,9 @@ -# Generated by Django 5.1 on 2024-08-13 14:36 +# Generated by Django 4.2 on 2024-08-14 14:46 import django.core.validators +from django.db import migrations, models import django.db.models.deletion import gestion.models -from django.db import migrations, models class Migration(migrations.Migration): @@ -24,6 +24,18 @@ class Migration(migrations.Migration): ('foto', models.ImageField(blank=True, null=True, upload_to='autores/')), ], ), + migrations.CreateModel( + name='Libro', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('titulo', models.CharField(max_length=200)), + ('fecha_publicacion', models.PositiveBigIntegerField(default=2024, validators=[django.core.validators.MinValueValidator(1984), gestion.models.max_value_current_year])), + ('descripcion', models.TextField(blank=True, null=True)), + ('archivo', models.FileField(upload_to='libros/')), + ('portada', models.ImageField(blank=True, null=True, upload_to='portadas/')), + ('autor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.autor')), + ], + ), migrations.CreateModel( name='ReyMotaUser', fields=[ @@ -43,16 +55,4 @@ class Migration(migrations.Migration): 'abstract': False, }, ), - migrations.CreateModel( - name='Libro', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('titulo', models.CharField(max_length=200)), - ('fecha_publicacion', models.PositiveBigIntegerField(default=2024, validators=[django.core.validators.MinValueValidator(1984), gestion.models.max_value_current_year])), - ('descripcion', models.TextField(blank=True, null=True)), - ('archivo', models.FileField(upload_to='libros/')), - ('portada', models.ImageField(blank=True, null=True, upload_to='portadas/')), - ('autor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.autor')), - ], - ), ] diff --git a/Libros/biblioteca/gestion/templatetags/filtros_de_entorno.py b/Libros/biblioteca/gestion/templatetags/filtros_de_entorno.py index 15058a1..c3a307b 100644 --- a/Libros/biblioteca/gestion/templatetags/filtros_de_entorno.py +++ b/Libros/biblioteca/gestion/templatetags/filtros_de_entorno.py @@ -5,7 +5,4 @@ register = template.Library() @register.filter def muestra_version(clave): - print("muestra_valor: ", clave) - - print("muestra_valor: ", os.getenv(clave)) return os.getenv(clave, '') diff --git a/Libros/entrypoint.sh b/Libros/entrypoint.sh new file mode 100755 index 0000000..0d30599 --- /dev/null +++ b/Libros/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +python manage.py flush --no-input +python manage.py migrate + +exec "$@" diff --git a/Libros/nginx/Dockerfile b/Libros/nginx/Dockerfile new file mode 100644 index 0000000..8328a0e --- /dev/null +++ b/Libros/nginx/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:1.25 + +RUN rm /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d diff --git a/Libros/nginx/Makefile b/Libros/nginx/Makefile new file mode 100644 index 0000000..6c52e2c --- /dev/null +++ b/Libros/nginx/Makefile @@ -0,0 +1,8 @@ +install: + + echo "Creando imagen con version ${IMG_NGINX_VERSION} para la arquitectura ${ARQUITECTURA}" + + docker build --no-cache -t registry.reymota.es/nginx-${ARQUITECTURA}:${IMG_NGINX_VERSION} . + docker push registry.reymota.es/nginx-${ARQUITECTURA}:${IMG_NGINX_VERSION} + + diff --git a/Libros/nginx/nginx.conf b/Libros/nginx/nginx.conf new file mode 100644 index 0000000..15353b2 --- /dev/null +++ b/Libros/nginx/nginx.conf @@ -0,0 +1,25 @@ +upstream libros { + server libros:8000; +} + +server { + + listen 80; + + location / { + proxy_pass http://libros; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_redirect off; + client_max_body_size 100M; + } + + location /static/ { + alias /app/biblioteca/staticfiles/; + } + + location /media/ { + alias /app/biblioteca/mediafiles/; + } + +} diff --git a/Libros/requirements.txt b/Libros/requirements.txt new file mode 100644 index 0000000..dc7bc4d --- /dev/null +++ b/Libros/requirements.txt @@ -0,0 +1,6 @@ +asgiref==3.8.1 +Django==4.2 +pillow==10.4.0 +sqlparse==0.5.1 +typing_extensions==4.12.2 +gunicorn==22.0.0 diff --git a/django-on-docker/.env.dev b/django-on-docker/.env.dev new file mode 100644 index 0000000..16f774a --- /dev/null +++ b/django-on-docker/.env.dev @@ -0,0 +1,10 @@ +DEBUG=1 +SECRET_KEY=foo +DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] +SQL_ENGINE=django.db.backends.postgresql +SQL_DATABASE=hello_django_dev +SQL_USER=hello_django +SQL_PASSWORD=hello_django +SQL_HOST=db +SQL_PORT=5432 +DATABASE=postgres diff --git a/django-on-docker/.env.prod b/django-on-docker/.env.prod new file mode 100644 index 0000000..e388e80 --- /dev/null +++ b/django-on-docker/.env.prod @@ -0,0 +1,10 @@ +DEBUG=0 +SECRET_KEY=change_me +DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] +SQL_ENGINE=django.db.backends.postgresql +SQL_DATABASE=hello_django_prod +SQL_USER=hello_django +SQL_PASSWORD=hello_django +SQL_HOST=db +SQL_PORT=5432 +DATABASE=postgres diff --git a/django-on-docker/.env.prod.db b/django-on-docker/.env.prod.db new file mode 100644 index 0000000..23f23a2 --- /dev/null +++ b/django-on-docker/.env.prod.db @@ -0,0 +1,3 @@ +POSTGRES_USER=hello_django +POSTGRES_PASSWORD=hello_django +POSTGRES_DB=hello_django_prod diff --git a/django-on-docker/K8S/db-deployment.yaml b/django-on-docker/K8S/db-deployment.yaml new file mode 100644 index 0000000..7284b38 --- /dev/null +++ b/django-on-docker/K8S/db-deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.34.0 (cbf2835db) + labels: + io.kompose.service: db + name: db +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: db + strategy: + type: Recreate + template: + metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.34.0 (cbf2835db) + labels: + io.kompose.service: db + spec: + containers: + - env: + - name: POSTGRES_DB + value: hello_django_dev + - name: POSTGRES_PASSWORD + value: hello_django + - name: POSTGRES_USER + value: hello_django + image: postgres:15 + name: db + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: postgres-data + restartPolicy: Always + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-data diff --git a/django-on-docker/K8S/env-dev-configmap.yaml b/django-on-docker/K8S/env-dev-configmap.yaml new file mode 100644 index 0000000..c04837d --- /dev/null +++ b/django-on-docker/K8S/env-dev-configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +data: + DATABASE: postgres + DEBUG: "1" + DJANGO_ALLOWED_HOSTS: localhost 127.0.0.1 [::1] + SECRET_KEY: foo + SQL_DATABASE: hello_django_dev + SQL_ENGINE: django.db.backends.postgresql + SQL_HOST: db + SQL_PASSWORD: hello_django + SQL_PORT: "5432" + SQL_USER: hello_django +kind: ConfigMap +metadata: + labels: + io.kompose.service: web-env-dev + name: env-dev diff --git a/django-on-docker/K8S/postgres-data-persistentvolumeclaim.yaml b/django-on-docker/K8S/postgres-data-persistentvolumeclaim.yaml new file mode 100644 index 0000000..27c2094 --- /dev/null +++ b/django-on-docker/K8S/postgres-data-persistentvolumeclaim.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + io.kompose.service: postgres-data + name: postgres-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi diff --git a/django-on-docker/K8S/pv-local-hello.yaml b/django-on-docker/K8S/pv-local-hello.yaml new file mode 100644 index 0000000..aef5bc3 --- /dev/null +++ b/django-on-docker/K8S/pv-local-hello.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: hello-pg-folder + labels: + app: hello +spec: + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/Externo/hello" + diff --git a/django-on-docker/K8S/web-cm0-configmap.yaml b/django-on-docker/K8S/web-cm0-configmap.yaml new file mode 100644 index 0000000..41d66ef --- /dev/null +++ b/django-on-docker/K8S/web-cm0-configmap.yaml @@ -0,0 +1,174 @@ +apiVersion: v1 +data: + Dockerfile: |+ + # pull official base image + FROM python:3.11.4-slim-buster + + # set work directory + WORKDIR /usr/src/app + + # set environment variables + ENV PYTHONDONTWRITEBYTECODE 1 + ENV PYTHONUNBUFFERED 1 + + # install system dependencies + RUN apt-get update && apt-get install -y netcat + + # install dependencies + RUN pip install --upgrade pip + COPY ./requirements.txt . + RUN pip install -r requirements.txt + + # copy entrypoint.sh + COPY ./entrypoint.sh . + RUN sed -i 's/\r$//g' /usr/src/app/entrypoint.sh + RUN chmod +x /usr/src/app/entrypoint.sh + + # copy project + COPY . . + + # run entrypoint.sh + ENTRYPOINT ["/usr/src/app/entrypoint.sh"] + + Dockerfile.prod: |+ + ########### + # BUILDER # + ########### + + # pull official base image + FROM python:3.11.4-slim-buster AS builder + + # set work directory + WORKDIR /usr/src/app + + # set environment variables + ENV PYTHONDONTWRITEBYTECODE 1 + ENV PYTHONUNBUFFERED 1 + + # install system dependencies + RUN apt-get update && \ + apt-get install -y --no-install-recommends gcc + + # lint + RUN pip install --upgrade pip + RUN pip install flake8==6.0.0 + COPY . /usr/src/app/ + RUN flake8 --ignore=E501,F401 . + + # install python dependencies + COPY ./requirements.txt . + RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt + + + ######### + # FINAL # + ######### + + # pull official base image + FROM python:3.11.4-slim-buster + + # create directory for the app user + RUN mkdir -p /home/app + + # create the app user + RUN addgroup --system app && adduser --system --group app + + # create the appropriate directories + ENV HOME=/home/app + ENV APP_HOME=/home/app/web + RUN mkdir $APP_HOME + RUN mkdir $APP_HOME/staticfiles + RUN mkdir $APP_HOME/mediafiles + WORKDIR $APP_HOME + + # install dependencies + RUN apt-get update && apt-get install -y --no-install-recommends netcat + COPY --from=builder /usr/src/app/wheels /wheels + COPY --from=builder /usr/src/app/requirements.txt . + RUN pip install --upgrade pip + RUN pip install --no-cache /wheels/* + + # copy entrypoint.prod.sh + COPY ./entrypoint.prod.sh . + RUN sed -i 's/\r$//g' $APP_HOME/entrypoint.prod.sh + RUN chmod +x $APP_HOME/entrypoint.prod.sh + + # copy project + COPY . $APP_HOME + + # chown all the files to the app user + RUN chown -R app:app $APP_HOME + + # change to the app user + USER app + + # run entrypoint.prod.sh + ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"] + + entrypoint.prod.sh: |+ + #!/bin/sh + + if [ "$DATABASE" = "postgres" ] + then + echo "Waiting for postgres..." + + while ! nc -z $SQL_HOST $SQL_PORT; do + sleep 0.1 + done + + echo "PostgreSQL started" + fi + + exec "$@" + + entrypoint.sh: |+ + #!/bin/sh + + if [ "$DATABASE" = "postgres" ] + then + echo "Waiting for postgres..." + + while ! nc -z $SQL_HOST $SQL_PORT; do + sleep 0.1 + done + + echo "PostgreSQL started" + fi + + #python manage.py flush --no-input + #python manage.py migrate + + exec "$@" + + manage.py: | + #!/usr/bin/env python + """Django's command-line utility for administrative tasks.""" + import os + import sys + + + def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hello_django.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + + if __name__ == '__main__': + main() + requirements.txt: | + Django==4.2.3 + psycopg2-binary==2.9.6 + gunicorn==21.2.0 +kind: ConfigMap +metadata: + labels: + io.kompose.service: web + name: web-cm0 diff --git a/django-on-docker/K8S/web-deployment.yaml b/django-on-docker/K8S/web-deployment.yaml new file mode 100644 index 0000000..9a58c82 --- /dev/null +++ b/django-on-docker/K8S/web-deployment.yaml @@ -0,0 +1,94 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.34.0 (cbf2835db) + labels: + io.kompose.service: web + name: web +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: web + strategy: + type: Recreate + template: + metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.34.0 (cbf2835db) + labels: + io.kompose.service: web + spec: + containers: + - args: + - python + - manage.py + - runserver + - 0.0.0.0:8000 + env: + - name: DATABASE + valueFrom: + configMapKeyRef: + key: DATABASE + name: env-dev + - name: DEBUG + valueFrom: + configMapKeyRef: + key: DEBUG + name: env-dev + - name: DJANGO_ALLOWED_HOSTS + valueFrom: + configMapKeyRef: + key: DJANGO_ALLOWED_HOSTS + name: env-dev + - name: SECRET_KEY + valueFrom: + configMapKeyRef: + key: SECRET_KEY + name: env-dev + - name: SQL_DATABASE + valueFrom: + configMapKeyRef: + key: SQL_DATABASE + name: env-dev + - name: SQL_ENGINE + valueFrom: + configMapKeyRef: + key: SQL_ENGINE + name: env-dev + - name: SQL_HOST + valueFrom: + configMapKeyRef: + key: SQL_HOST + name: env-dev + - name: SQL_PASSWORD + valueFrom: + configMapKeyRef: + key: SQL_PASSWORD + name: env-dev + - name: SQL_PORT + valueFrom: + configMapKeyRef: + key: SQL_PORT + name: env-dev + - name: SQL_USER + valueFrom: + configMapKeyRef: + key: SQL_USER + name: env-dev + image: web + name: web + ports: + - containerPort: 8000 + protocol: TCP + volumeMounts: + - mountPath: /usr/src/app + name: web-cm0 + restartPolicy: Always + volumes: + - configMap: + name: web-cm0 + name: web-cm0 diff --git a/django-on-docker/K8S/web-service.yaml b/django-on-docker/K8S/web-service.yaml new file mode 100644 index 0000000..25e48d5 --- /dev/null +++ b/django-on-docker/K8S/web-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.34.0 (cbf2835db) + labels: + io.kompose.service: web + name: web +spec: + ports: + - name: "8000" + port: 8000 + targetPort: 8000 + selector: + io.kompose.service: web diff --git a/django-on-docker/app/Dockerfile b/django-on-docker/app/Dockerfile new file mode 100644 index 0000000..27db7a9 --- /dev/null +++ b/django-on-docker/app/Dockerfile @@ -0,0 +1,29 @@ +# pull official base image +FROM python:3.11.4-slim-buster + +# set work directory +WORKDIR /usr/src/app + +# set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# install system dependencies +RUN apt-get update && apt-get install -y netcat + +# install dependencies +RUN pip install --upgrade pip +COPY ./requirements.txt . +RUN pip install -r requirements.txt + +# copy entrypoint.sh +COPY ./entrypoint.sh . +RUN sed -i 's/\r$//g' /usr/src/app/entrypoint.sh +RUN chmod +x /usr/src/app/entrypoint.sh + +# copy project +COPY . . + +# run entrypoint.sh +ENTRYPOINT ["/usr/src/app/entrypoint.sh"] + diff --git a/django-on-docker/app/Dockerfile.prod b/django-on-docker/app/Dockerfile.prod new file mode 100644 index 0000000..3b54491 --- /dev/null +++ b/django-on-docker/app/Dockerfile.prod @@ -0,0 +1,74 @@ +########### +# BUILDER # +########### + +# pull official base image +FROM python:3.11.4-slim-buster AS builder + +# set work directory +WORKDIR /usr/src/app + +# set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# install system dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends gcc + +# lint +RUN pip install --upgrade pip +RUN pip install flake8==6.0.0 +COPY . /usr/src/app/ +RUN flake8 --ignore=E501,F401 . + +# install python dependencies +COPY ./requirements.txt . +RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt + + +######### +# FINAL # +######### + +# pull official base image +FROM python:3.11.4-slim-buster + +# create directory for the app user +RUN mkdir -p /home/app + +# create the app user +RUN addgroup --system app && adduser --system --group app + +# create the appropriate directories +ENV HOME=/home/app +ENV APP_HOME=/home/app/web +RUN mkdir $APP_HOME +RUN mkdir $APP_HOME/staticfiles +RUN mkdir $APP_HOME/mediafiles +WORKDIR $APP_HOME + +# install dependencies +RUN apt-get update && apt-get install -y --no-install-recommends netcat +COPY --from=builder /usr/src/app/wheels /wheels +COPY --from=builder /usr/src/app/requirements.txt . +RUN pip install --upgrade pip +RUN pip install --no-cache /wheels/* + +# copy entrypoint.prod.sh +COPY ./entrypoint.prod.sh . +RUN sed -i 's/\r$//g' $APP_HOME/entrypoint.prod.sh +RUN chmod +x $APP_HOME/entrypoint.prod.sh + +# copy project +COPY . $APP_HOME + +# chown all the files to the app user +RUN chown -R app:app $APP_HOME + +# change to the app user +USER app + +# run entrypoint.prod.sh +ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"] + diff --git a/django-on-docker/app/entrypoint.prod.sh b/django-on-docker/app/entrypoint.prod.sh new file mode 100755 index 0000000..2cc61cd --- /dev/null +++ b/django-on-docker/app/entrypoint.prod.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +if [ "$DATABASE" = "postgres" ] +then + echo "Waiting for postgres..." + + while ! nc -z $SQL_HOST $SQL_PORT; do + sleep 0.1 + done + + echo "PostgreSQL started" +fi + +exec "$@" + diff --git a/django-on-docker/app/entrypoint.sh b/django-on-docker/app/entrypoint.sh new file mode 100755 index 0000000..cc7db59 --- /dev/null +++ b/django-on-docker/app/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +if [ "$DATABASE" = "postgres" ] +then + echo "Waiting for postgres..." + + while ! nc -z $SQL_HOST $SQL_PORT; do + sleep 0.1 + done + + echo "PostgreSQL started" +fi + +#python manage.py flush --no-input +#python manage.py migrate + +exec "$@" + diff --git a/django-on-docker/app/hello_django/__init__.py b/django-on-docker/app/hello_django/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django-on-docker/app/hello_django/asgi.py b/django-on-docker/app/hello_django/asgi.py new file mode 100644 index 0000000..37e4778 --- /dev/null +++ b/django-on-docker/app/hello_django/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for hello_django project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hello_django.settings') + +application = get_asgi_application() diff --git a/django-on-docker/app/hello_django/settings.py b/django-on-docker/app/hello_django/settings.py new file mode 100644 index 0000000..0545bed --- /dev/null +++ b/django-on-docker/app/hello_django/settings.py @@ -0,0 +1,137 @@ +import os +""" +Django settings for hello_django project. + +Generated by 'django-admin startproject' using Django 4.2.3. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ + +SECRET_KEY = os.environ.get("SECRET_KEY") + +DEBUG = bool(os.environ.get("DEBUG", default=0)) + +# 'DJANGO_ALLOWED_HOSTS' should be a single string of hosts with a space between each. +# For example: 'DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]' +ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ") + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'upload', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'hello_django.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'hello_django.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"), + "NAME": os.environ.get("SQL_DATABASE", BASE_DIR / "db.sqlite3"), + "USER": os.environ.get("SQL_USER", "user"), + "PASSWORD": os.environ.get("SQL_PASSWORD", "password"), + "HOST": os.environ.get("SQL_HOST", "localhost"), + "PORT": os.environ.get("SQL_PORT", "5432"), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.2/howto/static-files/ + +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / 'staticfiles' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +MEDIA_URL = "/media/" +MEDIA_ROOT = BASE_DIR / "mediafiles" + +CSRF_TRUSTED_ORIGINS = ["http://localhost:1337"] diff --git a/django-on-docker/app/hello_django/urls.py b/django-on-docker/app/hello_django/urls.py new file mode 100644 index 0000000..25e7dd0 --- /dev/null +++ b/django-on-docker/app/hello_django/urls.py @@ -0,0 +1,14 @@ +from django.contrib import admin +from django.urls import path +from django.conf import settings +from django.conf.urls.static import static + +from upload.views import image_upload + +urlpatterns = [ + path("", image_upload, name="upload"), + path("admin/", admin.site.urls), +] + +if bool(settings.DEBUG): + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/django-on-docker/app/hello_django/wsgi.py b/django-on-docker/app/hello_django/wsgi.py new file mode 100644 index 0000000..acd989d --- /dev/null +++ b/django-on-docker/app/hello_django/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for hello_django project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hello_django.settings') + +application = get_wsgi_application() diff --git a/django-on-docker/app/manage.py b/django-on-docker/app/manage.py new file mode 100755 index 0000000..b0c067d --- /dev/null +++ b/django-on-docker/app/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hello_django.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/django-on-docker/app/mediafiles/How to Install Kubernetes Cluster on Ubuntu 24.04 LTS (Step-by-Step Guide) by Hakan Bayraktar Medium.pdf b/django-on-docker/app/mediafiles/How to Install Kubernetes Cluster on Ubuntu 24.04 LTS (Step-by-Step Guide) by Hakan Bayraktar Medium.pdf new file mode 100644 index 0000000..5b5c946 Binary files /dev/null and b/django-on-docker/app/mediafiles/How to Install Kubernetes Cluster on Ubuntu 24.04 LTS (Step-by-Step Guide) by Hakan Bayraktar Medium.pdf differ diff --git a/django-on-docker/app/requirements.txt b/django-on-docker/app/requirements.txt new file mode 100644 index 0000000..42346ca --- /dev/null +++ b/django-on-docker/app/requirements.txt @@ -0,0 +1,3 @@ +Django==4.2.3 +psycopg2-binary==2.9.6 +gunicorn==21.2.0 diff --git a/django-on-docker/app/upload/__init__.py b/django-on-docker/app/upload/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django-on-docker/app/upload/admin.py b/django-on-docker/app/upload/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/django-on-docker/app/upload/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/django-on-docker/app/upload/apps.py b/django-on-docker/app/upload/apps.py new file mode 100644 index 0000000..2dcd356 --- /dev/null +++ b/django-on-docker/app/upload/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UploadConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'upload' diff --git a/django-on-docker/app/upload/migrations/__init__.py b/django-on-docker/app/upload/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django-on-docker/app/upload/models.py b/django-on-docker/app/upload/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/django-on-docker/app/upload/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/django-on-docker/app/upload/templates/upload.html b/django-on-docker/app/upload/templates/upload.html new file mode 100644 index 0000000..995141d --- /dev/null +++ b/django-on-docker/app/upload/templates/upload.html @@ -0,0 +1,13 @@ +{% block content %} + +
+ + {% if image_url %} +File uploaded at: {{ image_url }}
+ {% endif %} + +{% endblock %} diff --git a/django-on-docker/app/upload/tests.py b/django-on-docker/app/upload/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/django-on-docker/app/upload/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/django-on-docker/app/upload/views.py b/django-on-docker/app/upload/views.py new file mode 100644 index 0000000..d89b7bb --- /dev/null +++ b/django-on-docker/app/upload/views.py @@ -0,0 +1,15 @@ +from django.shortcuts import render +from django.core.files.storage import FileSystemStorage + + +def image_upload(request): + if request.method == "POST" and request.FILES["image_file"]: + image_file = request.FILES["image_file"] + fs = FileSystemStorage() + filename = fs.save(image_file.name, image_file) + image_url = fs.url(filename) + print(image_url) + return render(request, "upload.html", { + "image_url": image_url + }) + return render(request, "upload.html") diff --git a/django-on-docker/docker-compose.prod.yml b/django-on-docker/docker-compose.prod.yml new file mode 100644 index 0000000..c0291c4 --- /dev/null +++ b/django-on-docker/docker-compose.prod.yml @@ -0,0 +1,35 @@ +services: + web: + build: + context: ./app + dockerfile: Dockerfile.prod + command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000 + volumes: + - static_volume:/home/app/web/staticfiles + - media_volume:/home/app/web/mediafiles + expose: + - 8000 + env_file: + - ./.env.prod + depends_on: + - db + db: + image: postgres:15 + volumes: + - postgres_data:/var/lib/postgresql/data/ + env_file: + - ./.env.prod.db + nginx: + build: ./nginx + volumes: + - static_volume:/home/app/web/staticfiles + - media_volume:/home/app/web/mediafiles + ports: + - 1337:80 + depends_on: + - web + +volumes: + postgres_data: + static_volume: + media_volume: diff --git a/django-on-docker/docker-compose.yml b/django-on-docker/docker-compose.yml new file mode 100644 index 0000000..0971260 --- /dev/null +++ b/django-on-docker/docker-compose.yml @@ -0,0 +1,25 @@ +#version: '3.8' + +services: + web: + build: ./app + command: python manage.py runserver 0.0.0.0:8000 + volumes: + - ./app/:/usr/src/app/ + ports: + - 8000:8000 + env_file: + - ./.env.dev + depends_on: + - db + db: + image: postgres:15 + volumes: + - postgres_data:/var/lib/postgresql/data/ + environment: + - POSTGRES_USER=hello_django + - POSTGRES_PASSWORD=hello_django + - POSTGRES_DB=hello_django_dev + +volumes: + postgres_data: diff --git a/django-on-docker/nginx/Dockerfile b/django-on-docker/nginx/Dockerfile new file mode 100644 index 0000000..8328a0e --- /dev/null +++ b/django-on-docker/nginx/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:1.25 + +RUN rm /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d diff --git a/django-on-docker/nginx/nginx.conf b/django-on-docker/nginx/nginx.conf new file mode 100644 index 0000000..36d3790 --- /dev/null +++ b/django-on-docker/nginx/nginx.conf @@ -0,0 +1,26 @@ + +upstream hello_django { + server web:8000; +} + +server { + + listen 80; + + location / { + proxy_pass http://hello_django; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_redirect off; + client_max_body_size 100M; + } + + location /static/ { + alias /home/app/web/staticfiles/; + } + + location /media/ { + alias /home/app/web/mediafiles/; + } + +}