One place for hosting & domains

      Django

      How To Harden the Security of Your Production Django Project


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Developing a Django application can be a quick and clean experience, because its approach is flexible and scalable. Django also offers a variety of security-oriented settings that can help you seamlessly prepare your project for production. However, when it comes to production deployment, there are several ways to further secure your project. Restructuring your project by breaking up your settings will allow you to easily set up different configurations based on the environment. Leveraging dotenv for hiding environment variables or confidential settings will ensure you don’t release any details about your project that may compromise it.

      While implementing these different strategies and features might seem time-consuming at first, developing a practical workflow will allow you to deploy releases of your project without compromising on security, or your productivity.

      In this tutorial, you will leverage a security-oriented workflow for your Django development by implementing and configuring environment-based settings, dotenv, and Django’s built-in security settings. These features all complement each other and will result in a version of your Django project that is ready for different approaches you may take to deployment.

      Prerequisites

      Before you begin this guide you’ll need the following:

      Note: If you’re using an existing Django project, you may have different requirements. This tutorial suggests a particular project structure, however, you can also use each of the sections of this tutorial individually as needed.

      Step 1 — Restructuring Django’s Settings

      In this first step, you’ll start by rearranging your settings.py file into environment-specific configurations. This is a good practice when you need to move a project between different environments, for example, development and production. This arrangement will mean less reconfiguration for different environments; instead, you’ll use an environment variable to switch between configurations, which we’ll discuss later in the tutorial.

      Create a new directory called settings in your project’s sub-directory:

      • mkdir testsite/testsite/settings

      (As per the prerequisites we’re using testsite, but you can substitute your project’s name in here.)

      This directory will replace your current settings.py configuration file; all of your environment-based settings will be in separate files contained in this folder.

      In your new settings folder, create three Python files:

      • cd testsite/testsite/settings
      • touch base.py development.py production.py

      The development.py file will contain settings you’ll normally use during development. And production.py will contain settings for use on a production server. You should keep these separate because the production configuration will use settings that will not work in a development environment; for example, forcing the use of HTTPS, adding headers, and using a production database.

      The base.py settings file will contain settings that development.py and production.py will inherit from. This is to reduce redundancy and to help keep your code cleaner. These Python files will be replacing settings.py, so you’ll now remove settings.py to avoid confusing Django.

      While still in your settings directory, rename settings.py to base.py with the following command:

      • mv ../settings.py base.py

      You’ve just completed the outline of your new environment-based settings directory. Your project won’t understand your new configuration yet, so next, you’ll fix this.

      Step 2 — Using python-dotenv

      Currently Django will not recognize your new settings directory or its internal files. So, before you continue working with your environment-based settings, you need to make Django work with python-dotenv. This is a dependency that loads environment variables from a .env file. This means that Django will look inside a .env file in your project’s root directory to determine which settings configuration it will use.

      Go to your project’s root directory:

      Install python-dotenv:

      • pip install python-dotenv

      Now you need to configure Django to use dotenv. You’ll edit two files to do this: manage.py, for development, and wsgi.py, for production.

      Let’s start by editing manage.py:

      Add the following code:

      testsite/manage.py

      import os
      import sys
      import dotenv
      
      def main():
          os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development')
      
          if os.getenv('DJANGO_SETTINGS_MODULE'):
          os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')
      
          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()
      
      dotenv.load_dotenv(
          os.path.join(os.path.dirname(__file__), '.env')
      )
      

      Save and close manage.py and then open wsgi.py for editing:

      Add the following highlighted lines:

      testsite/testsite/wsgi.py

      
      import os
      import dotenv
      
      from django.core.wsgi import get_wsgi_application
      
      dotenv.load_dotenv(
          os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
      )
      
      os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development')
      
      if os.getenv('DJANGO_SETTINGS_MODULE'):
       os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')
      
      application = get_wsgi_application()
      

      The code you’ve added to both of these files does two things. First, whenever Django runs—manage.py for running development, wsgi.py for production—you’re telling it to look for your .env file. If the file exists, you instruct Django to use the settings file that .env recommends, otherwise you use the development configuration by default.

      Save and close the file.

      Finally, let’s create a .env in your project’s root directory:

      Now add in the following line to set the environment to development:

      testsite/.env

      DJANGO_SETTINGS_MODULE="testsite.settings.development"
      

      Note: Add .env to your .gitignore file so it is never included in your commits; you’ll use this file to contain data such as passwords and API keys that you do not want visible publicly. Every environment your project is running on will have its own .env with settings for that specific environment.

      It is recommended to create a .env.example to include in your project so you can easily create a new .env wherever you need one.

      So, by default Django will use testsite.settings.development, but if you change DJANGO_SETTINGS_MODULE to testsite.settings.production for example, it will start using your production configuration. Next, you’ll populate your development.py and production.py settings configurations.

      Step 3 — Creating Development and Production Settings

      Next you’ll open your base.py and add the configuration you want to modify for each environment in the separate development.py and production.py files. The production.py will need to use your production database credentials, so ensure you have those available.

      Note: It is up to you to determine which settings you need to configure, based on environment. This tutorial will only cover a common example for production and development settings (that is, security settings and separate database configurations).

      In this tutorial we’re using the Django project from the prerequisite tutorial as the example project. We’ll move settings from from base.py to development.py. Begin by opening development.py:

      • nano testsite/settings/development.py

      First, you will import from base.py—this file inherits settings from base.py. Then you’ll transfer the settings you want to modify for the development environment:

      testsite/testsite/settings/development.py

      from .base import *
      
      DEBUG = True
      
      DATABASES = {
          'default': {
              'ENGINE': 'django.db.backends.sqlite3',
              'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
          }
      }
      

      In this case, the settings specific to development are: DEBUG, you need this True in development, but not in production; and DATABASES, a local database instead of a production database. You’re using an SQLite database here for development.

      Note: For security purposes, Django’s DEBUG output will never display any settings that may contain the strings: API, KEY, PASS, SECRET, SIGNATURE, or TOKEN.

      This is to ensure secrets will not be revealed, if you accidentally deploy a project to production with DEBUG still enabled.

      With that being said, never deploy a project publicly with DEBUG enabled. It will only ever put the security of your project at risk.

      Next, let’s add to production.py:

      • nano testsite/settings/production.py

      Production will be similar to development.py, but with a different database configuration and DEBUG set to False:

      testsite/testsite/settings/production.py

      from .base import *
      
      DEBUG = False
      
      ALLOWED_HOSTS = []
      
      DATABASES = {
          'default': {
              'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'),
              'NAME': os.environ.get('SQL_DATABASE', os.path.join(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', ''),
          }
      }
      

      For the example database configuration given, you can use dotenv to configure each of the given credentials, with defaults included. Assuming you’ve already set up a database for the production version of your project, please use your configuration instead of the example provided.

      You have now configured your project to use different settings based on DJANGO_SETTINGS_MODULE in .env. Given the example settings you’ve used, when you set your project to use production settings, DEBUG becomes False, ALLOWED_HOSTS is defined, and you start using a different database that you’ve (already) configured on your server.

      Step 4 — Working with Django’s Security Settings

      Django includes security settings ready for you to add to your project. In this step, you’ll add security settings to your project that are considered essential for any production project. These settings are intended for use when your project is available to the public. It’s not recommended to use any of these settings in your development environment; hence, in this step you’re limiting these settings to the production.py configuration.

      For the most part these settings are going to enforce the use of HTTPS for various web features, such as session cookies, CSRF cookies, upgrading HTTP to HTTPS, and so on. Therefore, if you haven’t already set up your server with a domain pointing to it, hold off on this section for now. If you need to set up your server ready for deployment, check out the Conclusion for suggested articles on this.

      First open production.py:

      In your file, add the highlighted settings that work for your project, according to the explanations following the code:

      testsite/testsite/settings/production.py

      from .base import *
      
      DEBUG = False
      
      ALLOWED_HOSTS = ['your_domain', 'www.your_domain']
      
      DATABASES = {
          'default': {
              'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'),
              'NAME': os.environ.get('SQL_DATABASE', os.path.join(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', ''),
          }
      }
      
      SECURE_SSL_REDIRECT = True
      
      SESSION_COOKIE_SECURE = True
      
      CSRF_COOKIE_SECURE = True
      
      SECURE_BROWSER_XSS_FILTER = True
      
      • SECURE_SSL_REDIRECT redirects all HTTP requests to HTTPS (unless exempt). This means your project will always try to use an encrypted connection. You will need to have SSL configured on your server for this to work. Note that if you have Nginx or Apache configured to do this already, this setting will be redundant.
      • SESSION_COOKIE_SECURE tells the browser that cookies can only be handled over HTTPS. This means cookies your project produces for activities, such as logins, will only work over an encrypted connection.
      • CSRF_COOKIE_SECURE is the same as SESSION_COOKIE_SECURE but applies to your CSRF token. CSRF tokens protect against Cross-Site Request Forgery. Django CSRF protection does this by ensuring any forms submitted (for logins, signups, and so on) to your project were created by your project and not a third party.
      • SECURE_BROWSER_XSS_FILTER sets the X-XSS-Protection: 1; mode=block header on all responses that do not already have it. This ensures third parties cannot inject scripts into your project. For example, if a user stores a script in your database using a public field, when that script is retrieved and displayed to other users it will not run.

      If you would like to read more about the different security settings available within Django, check out their documentation.

      Warning: Django’s documentation states you shouldn’t rely completely on SECURE_BROWSER_XSS_FILTER. Never forget to validate and sanitize input.

      Additional Settings

      The following settings are for supporting HTTP Strict Transport Security (HSTS)—this means that your entire site must use SSL at all times.

      • SECURE_HSTS_SECONDS is the amount of time in seconds HSTS is set for. If you set this for an hour (in seconds), every time you visit a web page on your website, it tells your browser that for the next hour HTTPS is the only way you can visit the site. If during that hour you visit an insecure part of your website, the browser will show an error and the insecure page will be inaccessible.
      • SECURE_HSTS_PRELOAD only works if SECURE_HSTS_SECONDS is set. This header instructs the browser to preload your site. This means that your website will be added to a hardcoded list, which is implemented in popular browsers, like Firefox and Chrome. This requires that your website is always encrypted. It is important to be careful with this header. If at anytime you decide to not use encryption for your project, it can take weeks to be manually removed from the HSTS Preload list.
      • SECURE_HSTS_INCLUDE_SUBDOMAINS applies the HSTS header to all subdomains. Enabling this header, means that both your_domain and unsecure.your_domain will require encryption even if unsecure.your_domain is not related to this Django project.

      Warning: Incorrectly configuring these additional settings can break your site for a significant amount of time.

      Please read the Django documentation on HSTS before implementing these settings.

      It is necessary to consider how these settings will work with your own Django project; overall the settings discussed here are a good foundation for most Django projects. Next you’ll review some further usage of dotENV.

      Step 5 — Using python-dotenv for Secrets

      The final part of this tutorial will help you leverage python-dotenv. This will allow you to hide certain information such as your project’s SECRET_KEY or the admin’s login URL. This is a great idea if you intend to publish your code on platforms like GitHub or GitLab since these secrets won’t be published. Instead, whenever you initially set up your project on a local environment or a server, you can create a new .env file and define those secret variables.

      You must hide your SECRET_KEY so you’ll start working on that in this section.

      Open your .env file:

      And add the following line:

      testsite/.env

      DJANGO_SETTINGS_MODULE="django_hardening.settings.development"
      SECRET_KEY="your_secret_key"
      

      And in your base.py:

      • nano testsite/settings/base.py

      Let’s update the SECRET_KEY variable like so:

      testsite/testsite/settings/base.py

      . . .
      SECRET_KEY = os.getenv('SECRET_KEY')
      . . .
      

      Your project will now use the SECRET_KEY located in .env.

      Lastly, you’ll hide your admin URL by adding a long string of random characters to it. This will ensure bots can’t brute force the login fields and strangers can’t try guessing the login.

      Open .env again:

      And add a SECRET_ADMIN_URL variable:

      testsite/.env

      DJANGO_SETTINGS_MODULE="django_hardening.settings.development"
      SECRET_KEY="your_secret_key"
      SECRET_ADMIN_URL="very_secret_url"
      

      Now let’s tell Django to hide your admin URL with SECRET_ADMIN_URL:

      Note: Don’t forget to replace your_secret_key and very_secret_url with your own secret strings. Also don’t forget to replace very_secret_url with your own secret URL.

      If you want to use random strings for these variables, Python provides a fantastic secrets.py library for generating such strings. The examples they give are great ways to create small Python programs for generating secure random strings.

      Edit the admin URL like so:

      testsite/testsite/urls.py

      import os
      from django.contrib import admin
      from django.urls import path
      
      urlpatterns = [
          path(os.getenv('SECRET_ADMIN_URL') + '/admin/', admin.site.urls),
      ]
      

      You can now find the admin login page at very_secret_url/admin/ instead of just /admin/.

      Conclusion

      In this tutorial you have configured your current Django project for easy use with different environments. Your project now leverages python-dotenv for handling secrets and settings. And your production settings now have Django’s built-in security features enabled.

      If you’ve enabled all the recommended security components and re-implemented settings as directed, your project has these key features:

      • SSL/HTTPS for all communications (for example, subdomains, cookies, CSRF).
      • XSS (cross-site scripting) attacks prevention.
      • CSRF (cross-site request forgery) attacks prevention.
      • Concealed project secret key.
      • Concealed admin login URL preventing brute-force attacks.
      • Separate settings for development and production.

      If you are interested in learning more about Django, check out our tutorial series on Django development.

      Also, if you haven’t already put your project into production, here is a tutorial on How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 20.04. You can also check out our Django topic page for further tutorials.

      And, of course, please read over Django’s settings documentation, for further information.



      Source link

      How To Deploy a Django App on App Platform


      The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Django is a powerful web framework that allows you to deploy your Python applications or websites. Django includes many features such as authentication, a custom database ORM (Object-Relational Mapper), and an extensible plugin architecture. Django simplifies the complexities of web development, allowing you to focus on writing code.

      In this tutorial, you’ll configure a Django project and deploy it to DigitalOcean’s App Platform using GitHub.

      Prerequisites

      To complete this tutorial, you’ll need:

      Step 1 — Creating a Python Virtual Environment for your Project

      Before you get started, you need to set up our Python developer environment. You will install your Python requirements within a virtual environment for easier management.

      First, create a directory in your home directory that you can use to store all of your virtual environments:

      Now create your virtual environment using Python:

      • python3 -m venv ~/.venvs/django

      This will create a directory called django within your .venvs directory. Inside, it will install a local version of Python and a local version of pip. You can use this to install and configure an isolated Python environment for your project.

      Before you install your project’s Python requirements, you need to activate the virtual environment. You can do that by typing:

      • source ~.venvs/django/bin/activate

      Your prompt should change to indicate that you are now operating within a Python virtual environment. It will look something like this: (django)user@host:~$.

      With your virtual environment active, install Django, Gunicorn, whitenoise, dj-database-url, and the psycopg2 PostgreSQL adaptor with the local instance of pip:

      • pip install django gunicorn psycopg2-binary dj-database-url

      Note: When the virtual environment is activated (when your prompt has (django) preceding it), use pip instead of pip3, even if you are using Python 3. The virtual environment’s copy of the tool is always named pip, regardless of the Python version.

      These packages do the following:

      • django - Installs the Django framework and libraries
      • gunicorn - A tool for deploying Django with a WSGI
      • dj-database-url - A Django tool for parsing a database URL
      • psycopg2 - A PostgreSQL adapter that allows Django to connect to a PostgreSQL database

      Now that you have these packages installed, you will need to save these requirements and their dependencies so App Platform can install them later. You can do this using pip and saving the information to a requirements.txt file:

      • pip freeze > requirements.txt

      You should now have all of the software needed to start a Django project. You are almost ready to deploy.

      Step 2 — Creating the Django Project

      Create your project using the django-admin tool that was installed when you installed Django:

      • django-admin startproject django_app

      At this point, your current directory (django_app in your case) will have the following content:

      • manage.py: A Django project management script.
      • django_app/: The Django project package. This should contain the __init__.py, settings.py, urls.py, asgi.py, and wsgi.py files.

      This directory will be the root directory of your project and will be what we upload to GitHub. Navigate into this directory with the command:

      Let’s adjust some settings before deployment.

      Adjusting the Project Settings

      Now that you’ve created a Django project, you’ll need to modify the settings to ensure it will run properly in App Platform. Open the settings file in your text editor:

      • nano django_app/settings.py

      Let’s examine our configuration one step at a time.

      Reading Environment Variables

      First, you need to add the os import statement to be able to read environment variables:

      django_app/settings.py

      import os
      

      Setting the Secret Key

      Next, you need to modify the SECRET_KEY directive. This is set by Django on the initial project creation and will have a randomly generated default value. It is unsafe to keep this hardcoded value in the code once it’s pushed to GitHub, so you should either read this from an environment variable or generate it when the application is started. To do this, add the following import statement at the top of the settings file:

      django_app/settings.py

      from django.core.management.utils import get_random_secret_key
      

      Now modify the SECRET_KEY directive to read in the value from the environment variable DJANGO_SECRET_KEY or generate the key if it does not find said environment variable:

      django_app/settings.py

      SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", get_random_secret_key())
      

      Warning: If you don’t set this environment variable, then every time the app is re-deployed, this key will change. This can have adverse effects on cookies and will require users to log in again every time this key changes. You can generate a key using an online password generator.

      Setting Allowed Hosts

      Now locate the ALLOWED_HOSTS directive. This defines a list of the server’s addresses or domain names that may be used to connect to the Django instance. Any incoming request with a Host header that is not in this list will raise an exception. Django requires that you set this to prevent a certain class of security vulnerability.

      In the square brackets, list the IP addresses or domain names that are associated with your Django server. Each item should be listed in quotations with entries separated by a comma. If you wish requests for an entire domain and any subdomains, prepend a period to the beginning of the entry.

      App Platform supplies you with a custom URL as a default and then allows you to set a custom domain after you have deployed the application. Since you won’t know this custom URL until you have deployed the application, you should attempt to read the ALLOWED_HOSTS from an environment variable, so App Platform can inject this into your app when it launches.

      We’ll cover this process more in-depth in a later section. But for now, modify your ALLOWED_HOSTS directive to attempt to read the hosts from an environment variable. The environment variable can be set to either a single host or a comma-delimited list:

      django_app/settings.py

      ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "127.0.0.1,localhost").split(",")
      

      Setting the Debug Directive

      Next you should modify the DEBUG directive so that you can toggle this by setting an environment variable:

      django_app/settings.py

      DEBUG = os.getenv("DEBUG", "False") == "True"
      

      Here you used the getenv method to check for an environment variable named DEBUG. If this variable isn’t found, we should default to False for safety. Since environment variables will be read in as strings from App Platform, be sure to make a comparison to ensure that your variable is evaluated correctly.

      Setting the Development Mode

      Now create a new directive named DEVELOPMENT_MODE that will also be set as an environment variable. This is a helper variable that you will use to determine when to connect to your Postgres database and when to connect to a local SQLite database for testing. You’ll use this variable later when setting up the database connection:

      django_app/settings.py

      DEVELOPMENT_MODE = os.getenv("DEVELOPMENT_MODE", "False") == "True"
      

      Configuring Database Access

      Next, find the section that configures database access. It will start with DATABASES. The configuration in the file is for a SQLite database. App Platform allows you to create a PostgreSQL database for our project, so you need to adjust the settings to be able to connect to it.

      Warning: If you don’t change these settings and continue with the SQLite DB, your database will be erased after every new deployment. App Platform doesn’t maintain the disk when re-deploying applications, and your data will be lost.

      Change the settings with your PostgreSQL database information. You’ll read in the database connection information and credentials from an environment variable, DATABASE_URL, that will be provided by App Platform. Use the psycopg2 adaptor we installed with pip to have Django access a PostgreSQL database. You’ll use the dj-database-url package that was installed to get all of the necessary information from the database connection URL.

      To facilitate with development of your application locally, you’ll also use an if statement here to determine if DEVELOPMENT_MODE is set to True and which database should be accessed. By default, this will be set to False, and it will attempt to connect to a PostgreSQL database. You also don’t want Django attempting to make a database connection to the PostgreSQL database when attempting to collect the static files, so you’ll write an if statement to examine the command that was executed and not connect to a database if you determine that the command given was collectstatic. App Platform will automatically collect static files when the app is deployed.

      First, install the sys library so you can determine the command that was passed to manage.py and the dj_database_url library to be able to parse the URL passed in:

      django_app/settings.py

      . . .
      import os
      import sys
      import dj_database_url
      

      Next remove the current DATABASE directive block and replace it with this:

      django_app/settings.py

      if DEVELOPMENT_MODE is True:
          DATABASES = {
              "default": {
                  "ENGINE": "django.db.backends.sqlite3",
                  "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
              }
          }
      elif len(sys.argv) > 0 and sys.argv[1] != 'collectstatic':
          if os.getenv("DATABASE_URL", None) is None:
              raise Exception("DATABASE_URL environment variable not defined")
          DATABASES = {
              "default": dj_database_url.parse(os.environ.get("DATABASE_URL")),
          }
      
      

      Next, move down to the bottom of the file and add a setting indicating where the static files should be placed. When your Django app is deployed to App Platform, python manage.py collectstatic will be run automatically. Set the route to match the STATIC_URL directive in the settings file:

      django_app/settings.py

      . . .
      STATIC_URL = "/static/"
      STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
      

      Note: If you plan on storing static files in other locations outside of your individual Django-app static files, you will need to add an additional directive to your settings file. This directive will specify where to find these files. Be aware that these directories cannot share the same name as your STATIC_ROOT:

      django_app/settings.py

      . . .
      STATIC_URL = "/static/"
      STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
      STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"))
      

      Reviewing the Completed settings.py File

      Your completed file will look like this:

      from django.core.management.utils import get_random_secret_key
      from pathlib import Path
      import os
      import sys
      import dj_database_url
      
      # 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/3.1/howto/deployment/checklist/
      
      # SECURITY WARNING: keep the secret key used in production secret!
      SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", get_random_secret_key())
      
      # SECURITY WARNING: don't run with debug turned on in production!
      DEBUG = os.getenv("DEBUG", "False") == "True"
      
      ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "127.0.0.1,localhost").split(",")
      
      
      # Application definition
      
      INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
      ]
      
      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',
          "whitenoise.middleware.WhiteNoiseMiddleware",
      ]
      
      ROOT_URLCONF = 'masons_django_app.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 = 'masons_django_app.wsgi.application'
      
      
      # Database
      # https://docs.djangoproject.com/en/3.1/ref/settings/#databases
      DEVELOPMENT_MODE = os.getenv("DEVELOPMENT_MODE", "False") == "True"
      
      if DEVELOPMENT_MODE is True:
          DATABASES = {
              "default": {
                  "ENGINE": "django.db.backends.sqlite3",
                  "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
              }
          }
      elif len(sys.argv) > 0 and sys.argv[1] != 'collectstatic':
          if os.getenv("DATABASE_URL", None) is None:
              raise Exception("DATABASE_URL environment variable not defined")
          DATABASES = {
              "default": dj_database_url.parse(os.environ.get("DATABASE_URL")),
          }
      
      
      # Password validation
      # https://docs.djangoproject.com/en/3.1/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/3.1/topics/i18n/
      
      LANGUAGE_CODE = 'en-us'
      
      TIME_ZONE = 'UTC'
      
      USE_I18N = True
      
      USE_L10N = True
      
      USE_TZ = True
      
      
      # Static files (CSS, JavaScript, Images)
      # https://docs.djangoproject.com/en/3.1/howto/static-files/
      
      STATIC_URL = "/static/"
      STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
      STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)
      STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
      

      Save and close settings.py.

      You’ve now finished configuring your Django app to run on App Platform. Next, you’ll push the app to GitHub and deploy it to App Platform.

      Step 3 — Pushing the Site to GitHub

      DigitalOcean App Platform deploys your code from GitHub repositories, so the first thing you’ll need to do is get your site in a git repository and then push that repository to GitHub.

      First, initialize your Django project as a git repository:

      When you work on your Django app locally, certain files get added that are unnecessary for deployment. Let’s exclude that directory by adding it to Git’s ignore list. Create a new file called .gitignore:

      Now add the following code to the file:

      .gitignore

      db.sqlite3
      *.pyc
      

      Save and close the file.

      Now execute the following command to add files to your repository:

      • git add django_app/ manage.py requirements.txt static/

      Make your initial commit:

      • git commit -m "Initial Django App"

      Your files will commit:

      Output

      [master (root-commit) eda5d36] Initial Django App 8 files changed, 238 insertions(+) create mode 100644 django_app/__init__.py create mode 100644 django_app/asgi.py create mode 100644 django_app/settings.py create mode 100644 django_app/urls.py create mode 100644 django_app/wsgi.py create mode 100644 manage.py create mode 100644 requirements.txt create mode 100644 static/README.md

      Open your browser and navigate to GitHub, log in with your profile, and create a new repository called django-app. Create an empty repository without a README or license file.

      Once you’ve created the repository, return to the command line to push your local files to GitHub.

      First, add GitHub as a remote repository:

      • git remote add origin https://github.com/your_username/django-app

      Next, rename the default branch main, to match what GitHub expects:

      Finally, push your main branch to GitHub’s main branch:

      Your files will transfer:

      Output

      Enumerating objects: 12, done. Counting objects: 100% (12/12), done. Delta compression using up to 8 threads Compressing objects: 100% (9/9), done. Writing objects: 100% (12/12), 3.98 KiB | 150.00 KiB/s, done. Total 12 (delta 1), reused 0 (delta 0) remote: Resolving deltas: 100% (1/1), done. To github.com:yourUsername/django-app.git * [new branch] main -> main Branch 'main' set up to track remote branch 'main' from 'origin'.

      Enter your GitHub credentials when prompted to push your code.

      Your code is now on GitHub and accessible through a web browser. Now you will deploy your site to DigitalOcean’s App Platform.

      Step 4 — Deploying to DigitalOcean with App Platform

      Once the code is pushed, visit https://cloud.digitalocean.com/apps and click Launch Your App. You’ll be prompted to connect your GitHub account:

      Connect GitHub account

      Connect your account and allow DigitalOcean to access your repositories. You can choose to let DigitalOcean have access to all of your repositories or just to the ones you wish to deploy.

      Allow repository access

      Click Install and Authorize. You’ll be returned to your DigitalOcean dashboard to continue creating your app.

      Once your GitHub account is connected, select the your_account/django-app repository and click Next.

      Choose your repository

      Next, provide your app’s name, choose a region, and ensure the main branch is selected. Then ensure that Autodeploy code changes is checked. Click Next to continue.

      Choose a name, region, and branch

      DigitalOcean will detect that your project is a Python app and will automatically populate a partial run command.

      Python Application detected and partial run command populated

      Click the Edit link next to the Build and Run commands to complete the build command. Your completed build command needs to reference your project’s WSGI file. In this example, this is at django_app.wsgi. Your completed run command should be gunicorn --worker-tmp-dir /dev/shm django_app.wsgi.

      Completing the run command

      Next, you need to define the environment variables you declared in your project’s settings. App Platform has a concept of App-Wide Variables, which are environment variables that are provided by App Platform, such as APP_URL and APP_DOMAIN. The platform also maintains Component-Specific Variables, which are variables that are exported from your components. These will be useful for determining your APP_DOMAIN beforehand so you can properly set DJANGO_ALLOWED_HOSTS. You will also use these variables to copy configuration settings from your database.

      To read more about these different variables, consult the App Platform Environment Variable Documetation

      For your Django app to function, you need to set the following environment variables like so:

      • DJANGO_ALLOWED_HOSTS -> ${APP_DOMAIN}
        • This allows us to know the randomly generated URL that App Platform provides and provide it to our app
      • DATABASE_URL -> ${<NAME_OF_YOUR_DATABASE>.DATABASE_URL}
        • In this case, we’ll name our database db in the next step, so this should be ${db.DATABASE_URL}
      • DEBUG -> True
        • Set this to True for now to verify your app is functioning and set to False when it’s time for this app to be in production
      • DJANGO_SECRET_KEY -> <A RANDOM SECRET KEY>
        • You can either allow your app to generate this at every launch or pick a strong password with at least 32 characters to use as this key. Using a secure password generator is a good option for this
        • Don’t forget to click the Encrypt check box to ensure that your credentials are encrypted for safety

      Set environment variables

      To set up your database, click the Add a Database button. You will be presented with the option of selecting a small development database or integrating with a managed database elsewhere. For this deployment, select the development database and ensure that the name of the database is db. Once you’ve verified this, click the Add Database button.

      Add a database

      Click Next, and you’ll be directed to the Finalize and Launch screen where you’ll choose your plan. Be sure to select the appropriate plan to fit your needs, whether in Basic App or Professional App and then click Launch App at the bottom. Your app will build and deploy:

      App building and deploying

      Once the build process completes, the interface will show you a healthy site. Now you need to access your app’s console through the Console tab and perform the Django first launch tasks by running the following commands:

      • python manage.py migrate - This will perform your initial database migrations.
      • python manage.py createsuperuser - This will prompt you for some information to create an administrative user

      Perform initial Django tasks

      Once you are done with that, click on the link to your app provided by App Platform:

      Click on app link

      This link should take you to the standard initial Django page.

      Initial Django Page

      And now you have a Django app deployed to App Platform. Any changes that you make and push to GitHub will be automatically deployed.

      Step 5 — Deploying Your Static Files

      Now that you’ve deployed your app, you may notice that your static files aren’t being loaded if you have DEBUG set to False. Django doesn’t serve static files in production and instead wants you to deploy them using a web server or CDN. Luckily, App Platform can do just that. App Platform provides free static asset serving if you are running a service alongside it, as you are doing with your app. So you’re going to deploy your same Django app but as a static site this time.

      Once your app is deployed, add a static site component from the Components tab in your app.

      Add Static Site

      Select the same GitHub repository as your deployed Django service. Click Next to continue.

      Select GitHub Repo

      Next, provide your app’s name and ensure the main branch is selected. Click Next to continue.

      Set Static Site Name and Branch

      Your component will be detected as a Service, so you’ll want to change the type to Static Site. Essentially we’ll have Django gather our static files and serve them. Set the route to what you set your STATIC_URL directive in your settings file. We set our directive to /static/ so set the route to /static. Finally, your static files will be collected into Output Directory in your app to match your STATIC_ROOT setting in settings.py. Yours is set to staticfiles, so set Output Directory to that.

      Static Site Settings

      Click Next, and you’ll be directed to the Finalize and Launch screen. When static files are paired with a service, it is free, so you won’t see any change in your bill. Click Launch App and deploy your static files. Now, if you have Debug set to False, you’ll see your static files properly displayed. Note that this tutorial’s only static asset is a README.md file. You will likely have more than this.

      Conclusion

      In this tutorial, you set up a Django project and deployed it using DigitalOcean’s App Platform. Any changes you commit and push to your repository will be re-deployed, so you can now expand your application. You can find the example code for this tutorial in the DigitalOcean Sample Images Repository

      The example in this tutorial is a minimal Django project. Your app might have more applications and features, but the deployment process will be the same.



      Source link

      How To Deploy a Scalable and Secure Django Application with Kubernetes


      Introduction

      In this tutorial you’ll deploy a containerized Django polls application into a Kubernetes cluster.

      Django is a powerful web framework that can help you get your Python application off the ground quickly. It includes several convenient features like an object-relational mapper, user authentication, and a customizable administrative interface for your application. It also includes a caching framework and encourages clean app design through its URL Dispatcher and Template system.

      In How to Build a Django and Gunicorn Application with Docker, the Django Tutorial Polls application was modified according to the Twelve-Factor methodology for building scalable, cloud-native web apps. This containerized setup was scaled and secured with an Nginx reverse-proxy and Let’s Encrypt-signed TLS certificates in How To Scale and Secure a Django Application with Docker, Nginx, and Let’s Encrypt. In this final tutorial in the From Containers to Kubernetes with Django series, the modernized Django polls application will be deployed into a Kubernetes cluster.

      Kubernetes is a powerful open-source container orchestrator that automates the deployment, scaling and management of containerized applications. Kubernetes objects like ConfigMaps and Secrets allow you to centralize and decouple configuration from your containers, while controllers like Deployments automatically restart failed containers and enable quick scaling of container replicas. TLS encryption is enabled with an Ingress object and the ingress-nginx open-source Ingress Controller. The cert-manager Kubernetes add-on renews and issues certificates using the free Let’s Encrypt certificate authority.

      Prerequisites

      To follow this tutorial, you will need:

      • A Kubernetes 1.15+ cluster with role-based access control (RBAC) enabled. This setup will use a DigitalOcean Kubernetes cluster, but you are free to create a cluster using another method.
      • The kubectl command-line tool installed on your local machine and configured to connect to your cluster. You can read more about installing kubectl in the official documentation. If you are using a DigitalOcean Kubernetes cluster, please refer to How to Connect to a DigitalOcean Kubernetes Cluster to learn how to connect to your cluster using kubectl.
      • A registered domain name. This tutorial will use your_domain.com throughout. You can get one for free at Freenom, or use the domain registrar of your choice.
      • An ingress-nginx Ingress Controller and the cert-manager TLS certificate manager installed into your cluster and configured to issue TLS certificates. To learn how to install and configure an Ingress with cert-manager, please consult How to Set Up an Nginx Ingress with Cert-Manager on DigitalOcean Kubernetes.
      • An A DNS record with your_domain.com pointing to the Ingress Load Balancer’s public IP address. If you are using DigitalOcean to manage your domain’s DNS records, consult How to Manage DNS Records to learn how to create A records
      • An S3 object storage bucket such as a DigitalOcean Space to store your Django project’s static files and a set of Access Keys for this Space. To learn how to create a Space, consult the How to Create Spaces product documentation. To learn how to create Access Keys for Spaces, consult Sharing Access to Spaces with Access Keys. With minor changes, you can use any object storage service that the django-storages plugin supports.
      • A PostgreSQL server instance, database, and user for your Django app. With minor changes, you can use any database that Django supports.
      • A Docker Hub account and public repository. For more information on creating these, please see Repositories from the Docker documentation.
      • The Docker engine installed on your local machine. Please see How to Install and Use Docker on Ubuntu 18.04 to learn more.

      Once you have these components set up, you’re ready to begin with this guide.

      Step 1 — Cloning and Configuring the Application

      In this step we’ll clone the application code from GitHub and configure settings like database credentials and object storage keys.

      The application code and Dockerfile can be found in the polls-docker branch of the Django Tutorial Polls App GitHub repository. This repo contains code for the Django documentation’s sample Polls application, which teaches you how to build a polling application from scratch.

      The polls-docker branch contains a Dockerized version of this Polls app. To learn how the Polls app was modified to work effectively in a containerized environment, please see How to Build a Django and Gunicorn Application with Docker.

      Begin by using git to clone the polls-docker branch of the Django Tutorial Polls App GitHub repository to your local machine:

      • git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

      Navigate into the django-polls directory:

      This directory contains the Django application Python code, a Dockerfile that Docker will use to build the container image, as well as an env file that contains a list of environment variables to be passed into the container’s running environment. Inspect the Dockerfile:

      Output

      FROM python:3.7.4-alpine3.10 ADD django-polls/requirements.txt /app/requirements.txt RUN set -ex && apk add --no-cache --virtual .build-deps postgresql-dev build-base && python -m venv /env && /env/bin/pip install --upgrade pip && /env/bin/pip install --no-cache-dir -r /app/requirements.txt && runDeps="$(scanelf --needed --nobanner --recursive /env | awk '{ gsub(/,/, "nso:", $2); print "so:" $2 }' | sort -u | xargs -r apk info --installed | sort -u)" && apk add --virtual rundeps $runDeps && apk del .build-deps ADD django-polls /app WORKDIR /app ENV VIRTUAL_ENV /env ENV PATH /env/bin:$PATH EXPOSE 8000 CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi"]

      This Dockerfile uses the official Python 3.7.4 Docker image as a base, and installs Django and Gunicorn’s Python package requirements, as defined in the django-polls/requirements.txt file. It then removes some unnecessary build files, copies the application code into the image, and sets the execution PATH. Finally, it declares that port 8000 will be used to accept incoming container connections, and runs gunicorn with 3 workers, listening on port 8000.

      To learn more about each of the steps in this Dockerfile, please see Step 6 of How to Build a Django and Gunicorn Application with Docker.

      Now, build the image using docker build:

      We name the image polls using the -t flag and pass in the current directory as a build context, the set of files to reference when constructing the image.

      After Docker builds and tags the image, list available images using docker images:

      You should see the polls image listed:

      OutputREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
      polls               latest              80ec4f33aae1        2 weeks ago         197MB
      python              3.7.4-alpine3.10    f309434dea3a        8 months ago        98.7MB
      

      Before we run the Django container, we need to configure its running environment using the env file present in the current directory. This file will be passed into the docker run command used to run the container, and Docker will inject the configured environment variables into the container’s running environment.

      Open the env file with nano or your favorite editor:

      django-polls/env

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Fill in missing values for the following keys:

      • DJANGO_SECRET_KEY: Set this to a unique, unpredictable value, as detailed in the Django docs. One method of generating this key is provided in Adjusting the App Settings of the Scalable Django App tutorial.
      • DJANGO_ALLOWED_HOSTS: This variable secures the app and prevents HTTP Host header attacks. For testing purposes, set this to *, a wildcard that will match all hosts. In production you should set this to your_domain.com. To learn more about this Django setting, consult Core Settings from the Django docs.
      • DATABASE_USERNAME: Set this to the PostgreSQL database user created in the prerequisite steps.
      • DATABASE_NAME: Set this to polls or the name of the PostgreSQL database created in the prerequisite steps.
      • DATABASE_PASSWORD: Set this to the PostgreSQL user password created in the prerequisite steps.
      • DATABASE_HOST: Set this to your database’s hostname.
      • DATABASE_PORT: Set this to your database’s port.
      • STATIC_ACCESS_KEY_ID: Set this to your Space or object storage’s access key.
      • STATIC_SECRET_KEY: Set this to your Space or object storage’s access key Secret.
      • STATIC_BUCKET_NAME: Set this to your Space name or object storage bucket.
      • STATIC_ENDPOINT_URL: Set this to the appropriate Spaces or object storage endpoint URL, for example https://your_space_name.nyc3.digitaloceanspaces.com if your Space is located in the nyc3 region.

      Once you’ve finished editing, save and close the file.

      In the next step we’ll run the configured container locally and create the database schema. We’ll also upload static assets like stylesheets and images to object storage.

      Step 2 — Creating the Database Schema and Uploading Assets to Object Storage

      With the container built and configured, use docker run to override the CMD set in the Dockerfile and create the database schema using the manage.py makemigrations and manage.py migrate commands:

      • docker run --env-file env polls sh -c "python manage.py makemigrations && python manage.py migrate"

      We run the polls:latest container image, pass in the environment variable file we just modified, and override the Dockerfile command with sh -c "python manage.py makemigrations && python manage.py migrate", which will create the database schema defined by the app code.

      If you’re running this for the first time you should see:

      Output

      No changes detected Operations to perform: Apply all migrations: admin, auth, contenttypes, polls, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying polls.0001_initial... OK Applying sessions.0001_initial... OK

      This indicates that the database schema has successfully been created.

      If you’re running migrate a subsequent time, Django will perform a no-op unless the database schema has changed.

      Next, we’ll run another instance of the app container and use an interactive shell inside of it to create an administrative user for the Django project.

      • docker run -i -t --env-file env polls sh

      This will provide you with a shell prompt inside of the running container which you can use to create the Django user:

      • python manage.py createsuperuser

      Enter a username, email address, and password for your user, and after creating the user, hit CTRL+D to quit the container and kill it.

      Finally, we’ll generate the static files for the app and upload them to the DigitalOcean Space using collectstatic. Note that this may take a bit of time to complete.

      • docker run --env-file env polls sh -c "python manage.py collectstatic --noinput"

      After these files are generated and uploaded, you’ll receive the following output.

      Output

      121 static files copied.

      We can now run the app:

      • docker run --env-file env -p 80:8000 polls

      Output

      [2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0 [2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) [2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync [2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7 [2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8 [2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9

      Here, we run the default command defined in the Dockerfile, gunicorn --bind :8000 --workers 3 mysite.wsgi:application, and expose container port 8000 so that port 80 on your local machine gets mapped to port 8000 of the polls container.

      You should now be able to navigate to the polls app using your web browser by typing http://localhost in the URL bar. Since there is no route defined for the / path, you’ll likely receive a 404 Page Not Found error, which is expected.

      Navigate to http://localhost/polls to see the Polls app interface:

      Polls Apps Interface

      To view the administrative interface, visit http://localhost/admin. You should see the Polls app admin authentication window:

      Polls Admin Auth Page

      Enter the administrative username and password you created with the createsuperuser command.

      After authenticating, you can access the Polls app’s administrative interface:

      Polls Admin Main Interface

      Note that static assets for the admin and polls apps are being delivered directly from object storage. To confirm this, consult Testing Spaces Static File Delivery.

      When you are finished exploring, hit CTRL+C in the terminal window running the Docker container to kill the container.

      With the Django app Docker image tested, static assets uploaded to object storage, and database schema configured and ready for use with your app, you’re ready to upload your Django app image to an image registry like Docker Hub.

      Step 3 — Pushing the Django App Image to Docker Hub

      To roll your app out on Kubernetes, your app image must be uploaded to a registry like Docker Hub. Kubernetes will pull the app image from its repository and then deploy it to your cluster.

      You can use a private Docker registry, like DigitalOcean Container Registry, currently free in Early Access, or a public Docker registry like Docker Hub. Docker Hub also allows you to create private Docker repositories. A public repository allows anyone to see and pull the container images, while a private repository allows you to restrict access to you and your team members.

      In this tutorial we’ll push the Django image to the public Docker Hub repository created in the prerequisites. You can also push your image to a private repository, but pulling images from a private repository is beyond the scope of this article. To learn more about authenticating Kubernetes with Docker Hub and pulling private images, please see Pull an Image from a Private Registry from the Kubernetes docs.

      Begin by logging in to Docker Hub on your local machine:

      Output

      Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. Username:

      Enter your Docker Hub username and password to login.

      The Django image currently has the polls:latest tag. To push it to your Docker Hub repo, re-tag the image with your Docker Hub username and repo name:

      • docker tag polls:latest your_dockerhub_username/your_dockerhub_repo_name:latest

      Push the image to the repo:

      • docker push sammy/sammy-django:latest

      In this tutorial the Docker Hub username is sammy and the repo name is sammy-django. You should replace these values with your own Docker Hub username and repo name.

      You’ll see some output that updates as image layers are pushed to Docker Hub.

      Now that your image is available to Kubernetes on Docker Hub, you can begin rolling it out in your cluster.

      Step 4 — Setting Up the ConfigMap

      When we ran the Django container locally, we passed the env file into docker run to inject configuration variables into the runtime environment. On Kubernetes, configuration variables can be injected using ConfigMaps and Secrets.

      ConfigMaps should be used to store non-confidential configuration information like app settings, and Secrets should be used for sensitive information like API keys and database credentials. They are both injected into containers in a similar fashion, but Secrets have additional access control and security features like encryption at rest. Secrets also store data in base64, while ConfigMaps store data in plain text.

      To begin, create a directory called yaml in which we’ll store our Kubernetes manifests. Navigate into the directory.

      Open a file called polls-configmap.yaml in nano or your preferred text editor:

      • nano polls-configmap.yaml

      Paste in the following ConfigMap manifest:

      polls-configmap.yaml

      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: polls-config
      data:
        DJANGO_ALLOWED_HOSTS: "*"
        STATIC_ENDPOINT_URL: "https://your_space_name.space_region.digitaloceanspaces.com"
        STATIC_BUCKET_NAME: "your_space_name"
        DJANGO_LOGLEVEL: "info"
        DEBUG: "True"
        DATABASE_ENGINE: "postgresql_psycopg2"
      

      We’ve extracted the non-sensitive configuration from the env file modified in Step 1 and pasted it into a ConfigMap manifest. The ConfigMap object is called polls-config. Copy in the same values entered into the env file in the previous step.

      For testing purposes leave DJANGO_ALLOWED_HOSTS as * to disable Host header-based filtering. In a production environment you should set this to your app’s domain.

      When you’re done editing the file, save and close it.

      Create the ConfigMap in your cluster using kubectl apply:

      • kubectl apply -f polls-configmap.yaml

      Output

      configmap/polls-config created

      With the ConfigMap created, we’ll create the Secret used by our app in the next step.

      Step 5 — Setting Up the Secret

      Secret values must be base64-encoded, which means creating Secret objects in your cluster is slightly more involved than creating ConfigMaps. You can repeat the process from the previous step, manually base64-encoding Secret values and pasting them into a manifest file. You can also create them using an environment variable file, kubectl create, and the --from-env-file flag, which we’ll do in this step.

      We’ll once again use the env file from Step 1, removing variables inserted into the ConfigMap. Make a copy of the env file called polls-secrets in the yaml directory:

      • cp ../env ./polls-secrets

      Edit the file in your preferred editor:

      polls-secrets

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Delete all the variables inserted into the ConfigMap manifest. When you’re done, it should look like this:

      polls-secrets

      DJANGO_SECRET_KEY=your_secret_key
      DATABASE_NAME=polls
      DATABASE_USERNAME=your_django_db_user
      DATABASE_PASSWORD=your_django_db_user_password
      DATABASE_HOST=your_db_host
      DATABASE_PORT=your_db_port
      STATIC_ACCESS_KEY_ID=your_space_access_key
      STATIC_SECRET_KEY=your_space_access_key_secret
      

      Be sure to use the same values used in Step 1. When you’re done, save and close the file.

      Create the Secret in your cluster using kubectl create secret:

      • kubectl create secret generic polls-secret --from-env-file=poll-secrets

      Output

      secret/polls-secret created

      Here we create a Secret object called polls-secret and pass in the secrets file we just created.

      You can inspect the Secret using kubectl describe:

      • kubectl describe secret polls-secret

      Output

      Name: polls-secret Namespace: default Labels: <none> Annotations: <none> Type: Opaque Data ==== DATABASE_PASSWORD: 8 bytes DATABASE_PORT: 5 bytes DATABASE_USERNAME: 5 bytes DJANGO_SECRET_KEY: 14 bytes STATIC_ACCESS_KEY_ID: 20 bytes STATIC_SECRET_KEY: 43 bytes DATABASE_HOST: 47 bytes DATABASE_NAME: 5 bytes

      At this point you’ve stored your app’s configuration in your Kubernetes cluster using the Secret and ConfigMap object types. We’re now ready to deploy the app into the cluster.

      Step 6 — Rolling Out the Django App Using a Deployment

      In this step you’ll create a Deployment for your Django app. A Kubernetes Deployment is a controller that can be used to manage stateless applications in your cluster. A controller is a control loop that regulates workloads by scaling them up or down. Controllers also restart and clear out failed containers.

      Deployments control one or more Pods, the smallest deployable unit in a Kubernetes cluster. Pods enclose one or more containers. To learn more about the different types of workloads you can launch, please review An Introduction to Kubernetes.

      Begin by opening a file called polls-deployment.yaml in your favorite editor:

      • nano polls-deployment.yaml

      Paste in the following Deployment manifest:

      polls-deployment.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: polls-app
        labels:
          app: polls
      spec:
          replicas: 2
        selector:
          matchLabels:
            app: polls
        template:
          metadata:
            labels:
              app: polls
          spec:
            containers:
              - image: your_dockerhub_username/app_repo_name:latest
                name: polls
                envFrom:
                - secretRef:
                    name: polls-secret
                - configMapRef:
                    name: polls-config
                ports:
                  - containerPort: 8000
                    name: gunicorn
      

      Fill in the appropriate container image name, referencing the Django Polls image you pushed to Docker Hub in Step 2.

      Here we define a Kubernetes Deployment called polls-app and label it with the key-value pair app: polls. We specify that we’d like to run two replicas of the Pod defined below the template field.

      Using envFrom with secretRef and configMapRef, we specify that all the data from the polls-secret Secret and polls-config ConfigMap should be injected into the containers as environment variables. The ConfigMap and Secret keys become the environment variable names.

      Finally, we expose containerPort 8000 and name it gunicorn.

      To learn more about configuring Kubernetes Deployments, please consult Deployments from the Kubernetes documentation.

      When you’re done editing the file, save and close it.

      Create the Deployment in your cluster using kubectl apply -f:

      • kubectl apply -f polls-deployment.yaml
      • deployment.apps/polls-app created

      Check that the Deployment rolled out correctly using kubectl get:

      • kubectl get deploy polls-app

      Output

      NAME READY UP-TO-DATE AVAILABLE AGE polls-app 2/2 2 2 6m38s

      If you encounter an error or something isn’t quite working, you can use kubectl describe to inspect the failed Deployment:

      You can inspect the two Pods using kubectl get pod:

      Output

      NAME READY STATUS RESTARTS AGE polls-app-847f8ccbf4-2stf7 1/1 Running 0 6m42s polls-app-847f8ccbf4-tqpwm 1/1 Running 0 6m57s

      Two replicas of your Django app are now up and running in the cluster. To access the app, you need to create a Kubernetes Service, which we’ll do next.

      Step 7 — Allowing External Access using a Service

      In this step, you’ll create a Service for your Django app. A Kubernetes Service is an abstraction that allows you to expose a set of running Pods as a network service. Using a Service you can create a stable endpoint for your app that does not change as Pods die and are recreated.

      There are multiple Service types, including ClusterIP Services, which expose the Service on a cluster-internal IP, NodePort Services, which expose the Service on each Node at a static port called the NodePort, and LoadBalancer Services, which provision a cloud load balancer to direct external traffic to the Pods in your cluster (via NodePorts, which it creates automatically). To learn more about these, please see Service from the Kubernetes docs.

      In our final setup we’ll use a ClusterIP Service that is exposed using an Ingress and the Ingress Controller set up in the prerequisites for this guide. For now, to test that everything is functioning correctly, we’ll create a temporary NodePort Service to access the Django app.

      Begin by creating a file called polls-svc.yaml using your favorite editor:

      Paste in the following Service manifest:

      polls-svc.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: polls
        labels:
          app: polls
      spec:
        type: NodePort
        selector:
          app: polls
        ports:
          - port: 8000
            targetPort: 8000
      

      Here we create a NodePort Service called polls and give it the app: polls label. We then select backend Pods with the app: polls label and target their 8000 ports.

      When you’re done editing the file, save and close it.

      Roll out the Service using kubectl apply:

      • kubectl apply -f polls-svc.yaml

      Output

      service/polls created

      Confirm that your Service was created using kubectl get svc:

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE polls NodePort 10.245.197.189 <none> 8000:32654/TCP 59s

      This output shows the Service’s cluster-internal IP and NodePort (32654). To connect to the service, we need the external IP addresses for our cluster nodes:

      Output

      NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME pool-7no0qd9e0-364fd Ready <none> 27h v1.18.8 10.118.0.5 203.0.113.1 Debian GNU/Linux 10 (buster) 4.19.0-10-cloud-amd64 docker://18.9.9 pool-7no0qd9e0-364fi Ready <none> 27h v1.18.8 10.118.0.4 203.0.113.2 Debian GNU/Linux 10 (buster) 4.19.0-10-cloud-amd64 docker://18.9.9 pool-7no0qd9e0-364fv Ready <none> 27h v1.18.8 10.118.0.3 203.0.113.3 Debian GNU/Linux 10 (buster) 4.19.0-10-cloud-amd64 docker://18.9.9

      In your web browser, visit your Polls app using any Node’s external IP address and the NodePort. Given the output above, the app’s URL would be: http://203.0.113.1:32654/polls.

      You should see the same Polls app interface that you accessed locally in Step 1:

      Polls Apps Interface

      You can repeat the same test using the /admin route: http://203.0.113.1:32654/admin. You should see the same Admin interface as before:

      Polls Admin Auth Page

      At this stage, you’ve rolled out two replicas of the Django Polls app container using a Deployment. You’ve also created a stable network endpoint for these two replicas, and made it externally accessible using a NodePort Service.

      The final step in this tutorial is to secure external traffic to your app using HTTPS. To do this we’ll use the ingress-nginx Ingress Controller installed in the prerequisites, and create an Ingress object to route external traffic to the polls Kubernetes Service.

      Step 8 — Configuring HTTPS Using Nginx Ingress and cert-manager

      Kubernetes Ingresses allow you to flexibly route traffic from outside your Kubernetes cluster to Services inside of your cluster. This is accomplished using Ingress objects, which define rules for routing HTTP and HTTPS traffic to Kubernetes Services, and Ingress Controllers, which implement the rules by load balancing traffic and routing it to the appropriate backend Services.

      In the prerequisites you installed the ingress-nginx Ingress Controller and cert-manager TLS certificate automation add-on. You also set up staging and production ClusterIssuers for your domain using the Let’s Encrypt certificate authority, and created an Ingress to test certificate issuance and TLS encryption to two dummy backend Services. Before continuing with this step, you should delete the echo-ingress Ingress created in the prerequisite tutorial:

      • kubectl delete ingress echo-ingress

      If you’d like you can also delete the dummy Services and Deployments using kubectl delete svc and kubectl delete deploy, but this is not essential to complete this tutorial.

      You should also have created a DNS A record with your_domain.com pointing to the Ingress Load Balancer’s public IP address. If you’re using a DigitalOcean Load Balancer, you can find this IP address in the Load Balancers section of the Control Panel. If you are also using DigitalOcean to manage your domain’s DNS records, consult How to Manage DNS Records to learn how to create A records.

      If you’re using DigitalOcean Kubernetes, also ensure that you’ve implemented the workaround described in Step 5 of How to Set Up an Nginx Ingress with Cert-Manager on DigitalOcean Kubernetes.

      Once you have an A record pointing to the Ingress Controller Load Balancer, you can create an Ingress for your_domain.com and the polls Service.

      Open a file called polls-ingress.yaml using your favorite editor:

      Paste in the following Ingress manifest:

      [polls-ingress.yaml]
      apiVersion: networking.k8s.io/v1beta1
      kind: Ingress
      metadata:
        name: polls-ingress
        annotations:
          kubernetes.io/ingress.class: "nginx"
          cert-manager.io/cluster-issuer: "letsencrypt-staging"
      spec:
        tls:
        - hosts:
          - your_domain.com
          secretName: polls-tls
        rules:
        - host: your_domain.com
          http:
            paths:
            - backend:
                serviceName: polls
                servicePort: 8000
      

      We create an Ingress object called polls-ingress and annotate it to instruct the control plane to use the ingress-nginx Ingress Controller and staging ClusterIssuer. We also enable TLS for your_domain.com and store the certificate and private key in a secret called polls-tls. Finally, we define a rule to route traffic for the your_domain.com host to the polls Service on port 8000.

      When you’re done editing the file, save and close it.

      Create the Ingress in your cluster using kubectl apply:

      • kubectl apply -f polls-ingress.yaml

      Output

      ingress.networking.k8s.io/polls-ingress created

      You can use kubectl describe to track the state of the Ingress you just created:

      • kubectl describe ingress polls-ingress

      Output

      Name: polls-ingress Namespace: default Address: workaround.your_domain.com Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>) TLS: polls-tls terminates your_domain.com Rules: Host Path Backends ---- ---- -------- your_domain.com polls:8000 (10.244.0.207:8000,10.244.0.53:8000) Annotations: cert-manager.io/cluster-issuer: letsencrypt-staging kubernetes.io/ingress.class: nginx Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal CREATE 51s nginx-ingress-controller Ingress default/polls-ingress Normal CreateCertificate 51s cert-manager Successfully created Certificate "polls-tls" Normal UPDATE 25s nginx-ingress-controller Ingress default/polls-ingress

      You can also run a describe on the polls-tls Certificate to further confirm its successful creation:

      • kubectl describe certificate polls-tls

      Output

      . . . Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Issuing 3m33s cert-manager Issuing certificate as Secret does not exist Normal Generated 3m32s cert-manager Stored new private key in temporary Secret resource "polls-tls-v9lv9" Normal Requested 3m32s cert-manager Created new CertificateRequest resource "polls-tls-drx9c" Normal Issuing 2m58s cert-manager The certificate has been successfully issued

      This confirms that the TLS certificate was successfully issued and HTTPS encryption is now active for your_domain.com.

      Given that we used the staging ClusterIssuer, most web browsers won’t trust the fake Let’s Encrypt certificate that it issued, so navigating to your_domain.com will bring you to an error page.

      To send a test request, we’ll use wget from the command-line:

      • wget -O - http://your_domain.com/polls

      Output

      . . . ERROR: cannot verify your_domain.com's certificate, issued by ‘CN=Fake LE Intermediate X1’: Unable to locally verify the issuer's authority. To connect to your_domain.com insecurely, use `--no-check-certificate'.

      We’ll use the suggested --no-check-certificate flag to bypass certificate validation:

      • wget --no-check-certificate -q -O - http://your_domain.com/polls

      Output

      <link rel="stylesheet" type="text/css" href="https://your_space.nyc3.digitaloceanspaces.com/django-polls/static/polls/style.css"> <p>No polls are available.</p>

      This output shows the HTML for the /polls interface page, also confirming that the stylesheet is being served from object storage.

      Now that you’ve successfully tested certificate issuance using the staging ClusterIssuer, you can modify the Ingress to use the production ClusterIssuer.

      Open polls-ingress.yaml for editing once again:

      Modify the cluster-issuer annotation:

      [polls-ingress.yaml]
      apiVersion: networking.k8s.io/v1beta1
      kind: Ingress
      metadata:
        name: polls-ingress
        annotations:
          kubernetes.io/ingress.class: "nginx"
          cert-manager.io/cluster-issuer: "letsencrypt-prod"
      spec:
        tls:
        - hosts:
          - your_domain.com
          secretName: polls-tls
        rules:
        - host: your_domain.com
          http:
            paths:
            - backend:
                serviceName: polls
                servicePort: 8000
      

      When you’re done, save and close the file. Update the Ingress using kubectl apply:

      • kubectl apply -f polls-ingress.yaml

      Output

      ingress.networking.k8s.io/polls-ingress configured

      You can use kubectl describe certificate polls-tls and kubectl describe ingress polls-ingress to track the certificate issuance status:

      • kubectl describe ingress polls-ingress

      Output

      . . . Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal CREATE 23m nginx-ingress-controller Ingress default/polls-ingress Normal CreateCertificate 23m cert-manager Successfully created Certificate "polls-tls" Normal UPDATE 76s (x2 over 22m) nginx-ingress-controller Ingress default/polls-ingress Normal UpdateCertificate 76s cert-manager Successfully updated Certificate "polls-tls"

      The above output confirms that the new production certificate was successfully issued and stored in the polls-tls Secret.

      Navigate to your_domain.com/polls in your web browser to confirm that HTTPS encryption is enabled and everything is working as expected. You should see the Polls app interface:

      Polls Apps Interface

      Verify that HTTPS encryption is active in your web browser. If you’re using Google Chrome, arriving at the above page without any errors confirms that everything is working correctly. In addition, you should see a padlock in the URL bar. Clicking on the padlock will allow you to inspect the Let’s Encrypt certificate details.

      As a final cleanup task, you can optionally switch the polls Service type from NodePort to the internal-only ClusterIP type.

      Modify polls-svc.yaml using your editor:

      Change the type from NodePort to ClusterIP:

      polls-svc.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: polls
        labels:
          app: polls
      spec:
        type: ClusterIP
        selector:
          app: polls
        ports:
          - port: 8000
            targetPort: 8000
      

      When you’re done editing the file, save and close it.

      Roll out the changes using kubectl apply:

      • kubectl apply -f polls-svc.yaml --force

      Output

      service/polls configured

      Confirm that your Service was modified using kubectl get svc:

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE polls ClusterIP 10.245.203.186 <none> 8000/TCP 22s

      This output shows that the Service type is now ClusterIP. The only way to access it is via your domain and the Ingress created in this step.

      Conclusion

      In this tutorial you deployed a scalable, HTTPS-secured Django app into a Kubernetes cluster. Static content is served directly from object storage, and the number of running Pods can be quickly scaled up or down using the replicas field in the polls-app Deployment manifest.

      If you’re using a DigitalOcean Space, you can also enable delivery of static assets via a content delivery network and create a custom subdomain for your Space. Please consult Enabling CDN from How to Set Up a Scalable Django App with DigitalOcean Managed Databases and Spaces to learn more.

      To review the rest of the series, please visit our From Containers to Kubernetes with Django series page.



      Source link