Skip to content

Latest commit

 

History

History
1681 lines (1213 loc) · 60.1 KB

12.md

File metadata and controls

1681 lines (1213 loc) · 60.1 KB

第12章 部署

本章中包含如下小节:

  • 发布可复用的Django应用
  • 在预发布环境通过mod_wsgi部署Apache
  • 在生产环境通过mod_wsgi部署Apache
  • 在预发布环境中基于Nginx和Gunicorn部署
  • 在生产环境中基于Nginx和Gunicorn部署

引言

一旦有了可运行的网站或可复用应用,会希望让它对外开放。部署网站是通过Django开发中最复杂的活动,因为需要处理很多部分的内容:

  • 管理web服务器
  • 配置数据库
  • 对静态和媒体文件提供服务
  • 处理Django项目
  • 配置缓存
  • 设置email发送
  • 管理域名
  • 安排后台任务和定时任务
  • 配置持续集成
  • 根据项目规模和复杂度所需的其它任务

在较大的团队中,所有这些任务都是由运维工程师完成,它们要求对网络和计算机结构、管理Linux服务器、bash脚本编写、vim使用等有很深的理解。

专业网站通常具有开发、预发布和生产环境。每个环境都有特定的目的。开发环境用于创建项目。生成环境是对外网站托管的一个(或一组)服务器。预发布环境是一种类似于生成的系统,但用于检测新功能及在发布前进行优化。

技术要求

运行本章的代码要求安装最新稳定版的Python 3、MySQL或PostgreSQL数据库以及通过虚拟环境创建的Django项目。

可在GitHub仓库的Chapter12目录中查看本章的代码。

发布可复用的Django应用

Django文档有一个如何对可复用应用进行打包的教程,这些应用可在稍后在虚拟环境中通过pip进行安装。请通过https://docs.djangoproject.com/en/3.0/intro/reusable-apps/进行查看。

但有另一种打包和发布可复用Django应用的(有人认为更好)方式,使用为不同代码项目创建模板的工具,如新的Django CMS 网站、Flask网站或jQuery插件。一个可用的项目模板是cookiecutter-djangopackage。在本节中,我们将学习如何使用它来发布可复用的likes应用。

准备工作

通过虚拟环境新建一个项目并安装cookiecutter,如下:

(env)$ pip install cookiecutter~=1.7.0

如何实现...

按照如下步骤来发布likes应用:

  1. 启动一个新的Django应用项目,如下:

    (env)$ cookiecutter https://github.com/pydanny/cookiecutter-djangopackage.git
    

    或者因为这是一个GitHub托管的cookiecutter模板,我们可以使用短语法,如下:

    (env)$ cookiecutter gh:pydanny/cookiecutter-djangopackage
    
  2. 回答问题来创建应用模板,如下:

    full_name [Your full name here]: Aidas Bendoraitis 
    email [[email protected]]: [email protected] 
    github_username [yourname]: archatas
    project_name [Django Package]: django-likes 
    repo_name [dj-package]: django-likes
    app_name [django_likes]: likes
    app_config_name [LikesConfig]:
    project_short_description [Your project description goes here]: Django app for liking anything on your website.
    models [Comma-separated list of models]: Like
    django_versions [1.11,2.1]: master
    version [0.1.0]:
    create_example_project [N]:
    Select open_source_license:
    1 - MIT
    2 - BSD
    3 - ISCL
    4 - Apache Software License 2.0
    5 - Not open source
    Choose from 1, 2, 3, 4, 5 [1]:
    

    这会对可发布的Django包创建一个基本文件结构,类似下面这样:

    django-likes/
    ├── docs/
    │   ├── Makefile
    │   ├── authors.rst
    │   ├── conf.py
    │   ├── contributing.rst
    │   ├── history.rst
    │   ├── index.rst
    │   ├── installation.rst
    │   ├── make.bat
    │   ├── readme.rst
    │   └── usage.rst
    ├── likes/
    │   ├── static/
    │   │   ├── css/
    │   │   │   └── likes.css
    │   │   ├── img/
    │   │   └── js/
    │   │       └── likes.js
    │   ├── templates/
    │   │   └── likes/
    │   │       └── base.html
    │   └── test_utils/
    │       ├── test_app/
    |       │   ├── migrations/
    │       │   │   └── __init__.py
    │       │   ├── __init__.py
    │       │   ├── admin.py
    │       │   ├── apps.py
    │       │   └── models.html
    │       ├── __init__.py
    │       ├── admin.py
    │       ├── apps.py
    │       ├── models.py
    │       ├── urls.py
    │       └── views.py
    ├── tests/
    │   ├── __init__.py
    │   ├── README.md
    │   ├── requirements.txt
    │   ├── settings.py
    │   ├── test_models.py
    │   └── urls.py
    ├── .coveragerc
    ├── .editorconfig
    ├── .gitignore
    ├── .travis.yml
    ├── AUTHORS.rst
    ├── CONTRIBUTING.rst
    ├── HISTORY.rst
    ├── LICENSE
    ├── MANIFEST.in
    ├── Makefile
    ├── README.rst
    ├── manage.py
    ├── requirements.txt
    ├── requirements_dev.txt
    ├── requirements_test.txt
    ├── runtests.py
    ├── setup.cfg
    ├── setup.py*
    └── tox.ini
    
  3. 拷贝Django项目的likes应用的文件拷贝到django-likes/likes目录中。在cookiecutter创建相同的文件时,需要合并内容,而不是重写。例如,likes/init.py文件需要包含一个版本字符串来在后续的步骤中通过 setup.py正常的运行,如下:

    # django-likes/likes/__init__.py
    __version__ = '0.1.0'
    
  4. 重新处理依赖,这样里面没有对Django项目的依赖,所有使用的函数和类都处于这个应用中。例如,在likes应用中,我们有对core应用的一些mixins的依赖。需要将相关代码拷贝到django-likes应用中。

    ℹ️或者,如果有很多依赖代码,我们可以用一个非耦合包发布core应用,但这样我们就需要单独进行维护了。

  5. 将可复用应用项目使用此前输入的repo_name添加到GitHub的Git仓库中。

  6. 查看各个文件并完成证书、README、文档、配置及其它文件。

  7. 确保应用通过cookiecutter模板测试:

    (env)$ pip install -r requirements_test.txt
    (env)$ python runtests.py
    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    . 
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    OK
    Destroying test database for alias 'default'...
    
  8. 如果你的包是闭源的,创建一个可分享的ZIP压缩包,如下:

    (env)$ python setup.py sdist
    

    这会创建一个django-likes/dist/django-likes-0.1.0.tar.gz文件,然后可在任意项目的虚拟环境中通过pip进行安装和卸载,如下:

    (env)$ pip install django-likes-0.1.0.tar.gz 
    (env)$ pip uninstall django-likes
    
  9. 如果你的包是开源的,可以注册并将应用发布至Python安装包索引(PyPI):

    (env)$ python setup.py register 
    (env)$ python setup.py publish
    
  10. 同时要让更多人知晓,可以应用通过https://www.djangopackages.com/packages/add/中的表单添加至Django安装包中。

实现原理...

Cookiecutter在Django应用项目模板的不同地方添加入请求数据,如果不想要输入内容可以按下Enter键使用 [方括号] 内所给定的默认值。这样就可以获取能免发布到Python安装包索引的setup.py、Sphinx文档、默认证书为MIT、项目的统一文本编辑器配置、应用中所包含的静态文件和模块以及其它有益的内容。

相关内容

在预发布环境通过mod_wsgi部署Apache

本节中我们将展示如何创建一个脚本,来在电脑的虚拟机中的预发布环境中部署项目。这一项目会使用带有mod_wsgi模块的Apache网页服务器。安装中我们会使用到Ansible、Vagrant和VirtualBox。前面提到过有很多细节需要处理,并且通常需要花几天来部署类似于些的优化部署脚本。

准备工作

按照https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/页面所列的部署清单、确保配置通过所有的安全推荐。至少要保证在运行如下命令时项目配置不会抛出警告:

(env)$ python manage.py check --deploy --settings=myproject.settings.staging

安装最新的稳定版Ansible、Vagrant和VirtualBox。可以通过如下的官网进行获取:

macOS X中可以使用HomeBrew来进行安装:

$ brew install ansible
$ brew cask install virtualbox
$ brew cask install vagrant

如何实现...

首先,需要对服务器上使用的不同服务创建配置模板。预发布和生产部署过程中都会使用到:

  1. 在Django项目中,创建一个deployment目录,并在其内创建一个ansible_templates目录。

  2. 创建一个用于时区配置的Jinja模板文件:

    {# deployment/ansible_templates/timezone.j2 #}
    {{ timezone }}
    
  3. 在配置SSL证书前为Apache域名配置创建一个Jinja模板文件:

    {# deployment/ansible_templates/apache_site-pre.conf.j2 #}
    <VirtualHost *:80>
        ServerName {{ domain_name }}
        ServerAlias {{ domain_name }} www.{{ domain_name }}
    
        DocumentRoot {{ project_root }}/public_html 
        DirectoryIndex index.html
        
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
    
        AliasMatch ^/.well-known/(.*) "/var/www/letsencrypt/$1"
    
        <Directory "/var/www/letsencrypt">
            Require all granted
        </Directory>
    
        <Directory "/">
            Require all granted
        </Directory>
        
    </VirtualHost>
    
  4. 为包含证书的Apache域名配置创建Jinja模板文件deployment/ansible_templates/apache_site.conf.j2 。这个文件中的内容可从https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-apache/ansible_templates/apache_site.conf.j2进行拷贝。

  5. 使用https://github.com/postgres/postgres/blob/REL_10_STABLE/src/backend/utils/misc/postgresql.conf.sample的内容为PostgreSQL配置创建模板文件deployment/ansible_templates/postgresql.j2。稍后可以对配置进行修改在匹配服务的要求。

  6. 为PostgreSQL的权限配置文件创建模板(当前授权较大,稍后可根据自己的需求进行修改):

    {# deployment/ansible_templates/pg_hba.j2 #}
    # TYPE  DATABASE        USER           CIDR-ADDRESS    METHOD
    local   all             all                            ident
    host    all             all            ::0/0           md5
    host    all             all            0.0.0.0/32      md5
    host    {{ db_name }}   {{ db_user }}  127.0.0.1/32    md5
    
  7. 为Postfix邮件服务配置创建模板:

    {# deployment/ansible_templates/postfix.j2 #}
    # See /usr/share/postfix/main.cf.dist for a commented, more 
    # complete version
    
    
    # Debian specific:  Specifying a file name will cause the first
    # line of that file to be used as the name.  The Debian default
    # is /etc/mailname.
    # myorigin = /etc/mailname
    
    smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
    biff = no
    
    # appending .domain is the MUA's job.
    append_dot_mydomain = no
    
    # Uncomment the next line to generate "delayed mail" warnings
    #delay_warning_time = 4h
    
    readme_directory = no
    
    # TLS parameters
    smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
    smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
    smtpd_use_tls=yes
    smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
    smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
    
    # See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc 
    # package for information on enabling SSL 
    # in the smtp client.
    
    smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
    myhostname = {{ domain_name }}
    alias_maps = hash:/etc/aliases
    alias_database = hash:/etc/aliases
    mydestination = $myhostname, localhost, localhost.localdomain, , 
     localhost
    relayhost =
    mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
    mailbox_size_limit = 0
    recipient_delimiter = +
    inet_interfaces = all
    inet_protocols = all
    virtual_alias_domains = {{ domain_name }}
    virtual_alias_maps = hash:/etc/postfix/virtual
    
  8. 为email转发配置创建模板:

    {# deployment/ansible_templates/virtual.j2 #}
    # /etc/postfix/virtual
    
    hello@{{ domain_name }} [email protected] 
    @{{ domain_name }} [email protected]
    
  9. 为memcached配置创建模板:

    {# deployment/ansible_templates/memcached.j2 #}
    # memcached default config file
    # 2003 - Jay Bonci <[email protected]>
    # This configuration file is read by the start-memcached script 
    # provided as part of the Debian GNU/Linux
    # distribution.
    
    # Run memcached as a daemon. This command is implied, and is not 
    # needed for the daemon to run. See the README.Debian that
    # comes with this package for more information. 
    -d
    
    # Log memcached's output to /var/log/memcached 
    logfile /var/log/memcached.log
    
    # Be verbose 
    # -v
    
    # Be even more verbose (print client commands as well) 
    # -vv
    
    # Use 1/16 of server RAM for memcached
    -m {{ (ansible_memtotal_mb * 0.0625) | int }}
    
    # Default connection port is 11211 
    -p 11211
    
    # Run the daemon as root. The start-memcached will default to 
    # running as root if no -u command is present
    # in this config file
    -u memcache
    
    # Specify which IP address to listen on. The default is to 
    # listen on all IP addresses
    # This parameter is one of the only security measures that 
    # memcached has, so make sure it's listening on
    # a firewalled interface. 
    -l 127.0.0.1
    
    # Limit the number of simultaneous incoming connections. 
    # The daemon default is 1024
    # -c 1024
    
    # Lock down all paged memory. Consult with the README and 
    # homepage before you do this
    # -k
    
    # Return error when memory is exhausted (rather than 
    # removing items)
    # -M
    
    # Maximize core file limit 
    # -r
    
  10. 最后为secrets.json文件创建一个Jinja模板:

    {# deployment/ansible_templates/secrets.json.j2 #}
    {
        "DJANGO_SECRET_KEY": "{{ django_secret_key }}", 
        "DATABASE_ENGINE": "django.contrib.gis.db.backends.postgis", 
        "DATABASE_NAME": "{{ db_name }}",
        "DATABASE_USER": "{{ db_user }}",
        "DATABASE_PASSWORD": "{{ db_password }}",
        "EMAIL_HOST": "{{ email_host }}",
        "EMAIL_PORT": "{{ email_port }}",
        "EMAIL_HOST_USER": "{{ email_host_user }}", 
        "EMAIL_HOST_PASSWORD": "{{ email_host_password }}"
    }
    

现在操作针对预发布环境的Vagrant和Ansible脚本:

  1. 在.gitignore文件中,添加代码行在忽略Vagrant和Ansible相关的文件:

    # .gitignore
    # Secrets 
    secrets.json 
    secrets.yml
    
    # Vagrant / Ansible 
    .vagrant
    *.retry
    
  2. 创建两个目录deployment/staging 和 deployment/staging/ansible。

  3. 在其中创建Vagrantfile文件来配置Ubuntu 18虚拟机并在其中运行Ansible脚本:

    # deployment/staging/ansible/Vagrantfile
    VAGRANTFILE_API_VERSION = "2"
    
    Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 
        config.vm.box = "bento/ubuntu-18.04" 
        config.vm.box_version = "201912.14.0" 
        config.vm.box_check_update = false 
        config.ssh.insert_key=false
        config.vm.provider "virtualbox" do |v| 
            v.memory = 512
            v.cpus = 1
            v.name = "myproject"
        end
        config.vm.network "private_network", ip: "192.168.50.5" 
        config.vm.provision "ansible" do |ansible|
            ansible.limit = "all"
            ansible.playbook = "setup.yml" 
            ansible.inventory_path = "./hosts/vagrant" 
            ansible.host_key_checking = false
            ansible.verbose = "vv"
            ansible.extra_vars = { ansible_python_interpreter: "/usr/bin/python3" }
        end 
    end
    
  4. 创建hosts目录,其中包含vagrant文件,内容如下:

     # deployment/staging/ansible/hosts/vagrant
    [servers] 
    192.168.50.5
    
  5. 创建一个vars.yml文件,其中包含在安装脚本和用于配置的Jinja脚本中会使用到的变量:

    # deployment/staging/ansible/vars.yml
    ---
    # a unix path-friendly name (IE, no spaces or special characters) 
    project_name: myproject
    
    user_username: "{{ project_name }}"
    
    # the base path to install to. You should not need to change this. 
    install_root: /home
    
    project_root: "{{ install_root }}/{{ project_name }}"
    
    # the python module path to your project's 
    wsgi file wsgi_module: myproject.wsgi
    
    # any directories that need to be added to the PYTHONPATH. 
    python_path: "{{ project_root }}/src/{{ project_name }}"
    
    # the git repository URL for the project
    project_repo: [email protected]:archatas/django-myproject.git
    
    # The value of your django project's STATIC_ROOT settings. 
    static_root: "{{ python_path }}/static"
    media_root: "{{ python_path }}/media"
    
    locale: en_US.UTF-8 
    timezone: Europe/Berlin
    
    domain_name: myproject.192.168.50.5.xip.io 
    django_settings: myproject.settings.staging
    
    letsencrypt_email: "" 
    wsgi_file_name: wsgi_staging.py
    
  6. 我们还需要带有密码和认证密钥等私密值的secrets.yml文件。首先创建一个不含敏感信息的sample_secrets.yml文件,其中只包含变量名,将其拷贝到secrets.yml之中并填写密钥信息。前一个文件处于版本控制之下,而后一个文件则进行忽略:

    # deployment/staging/ansible/sample_secrets.yml
    # Django Secret Key
    django_secret_key: "change-this-to-50-characters-long-random-string"
    
    # PostgreSQL database settings
    db_name: "myproject"
    db_user: "myproject"
    db_password: "change-this-to-a-secret-password" 
    db_host: "localhost"
    db_port: "5432"
    
    # Email SMTP settings
    email_host: "localhost"
    email_port: "25"
    email_host_user: ""
    email_host_password: ""
    
    # a private key that has access to the repository URL 
    ssh_github_key: ~/.ssh/id_rsa_github
    
  7. 在deployment/staging/ansible/setup.yml中创建Ansible脚本(称之为playbook),用于安装所有依赖及配置服务。通过https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-apache/staging/ansible/setup.yml拷贝这个文件的内容。

  8. 然后在deployment/staging/ansible/deploy.yml中创建另一个Ansible脚本用于处理Django项目。从https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-apache/staging/ansible/deploy.yml中拷贝这个文件的内容。

  9. 创建用于执行启动部署的bash脚本:

    # deployment/staging/ansible/setup_on_virtualbox.sh
    #!/usr/bin/env bash
    echo "=== Setting up the local staging server ===" 
    date
    
    cd "$(dirname "$0")" 
    vagrant up --provision
    
  10. 为bash脚本添加执行权限并运行:

    $ chmod +x setup_on_virtualbox.sh 
    $ ./setup_on_virtualbox.sh
    
  11. 如果脚本因报错而失败,很可能要重启虚拟机来让修改生效。可以通过ssh连接到虚拟机,修改为root用户,然后进行重启,如下:

    $ vagrant ssh
    Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-72-generic x86_64)
    
     * Documentation:  https://help.ubuntu.com
     * Management:     https://landscape.canonical.com
     * Support:        https://ubuntu.com/advantage
    
      System information as of Wed Jan 15 04:44:42 CET 2020
    
      System load:  0.21              Processes:           126
      Usage of /:   4.0% of 61.80GB   Users logged in:     1
      Memory usage: 35%               IP address for eth0: 10.0.2.15
      Swap usage:   4%                IP address for eth1: 192.168.50.5
    
    
    0 packages can be updated.
    0 updates are security updates.
    
    
    *** System restart required ***
    
    This system is built by the Bento project by Chef Software
    More information can be found at https://github.com/chef/bento
    Last login: Wed Jan 15 04:43:32 2020 from 192.168.50.1
    vagrant@myproject:~$ sudo su
    root@myproject:/home/vagrant#
    reboot
    Connection to 127.0.0.1 closed by remote host.
    Connection to 127.0.0.1 closed.
    
  12. 要浏览Django项目目录,ssh连接至虚拟机并修改用户为myproject,如下:

    $ vagrant ssh
    Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-74-generic x86_64) 
    #...
    vagrant@myproject:~$ sudo su - myproject
    (env) myproject@myproject:~$ pwd
    /home/myproject
    (env) myproject@myproject:~$ ls
    commands db_backups logs public_html src env
    

实现原理...

VirtualBox让我们可以在一台电脑上安装多个不操作系统的虚拟主机。Vagrant是一个创建这些虚拟主机以及使用脚本在其中下载和安装操作系统的工具。Ansible是一个基于Python工具,它读取.yaml配置文件中的指令并在远程服务器上进行执行。

我们所编写的部署脚本执行如下操作:

  • 在VirtualBox中创建虚拟机并安装Ubuntu 18
  • 为虚拟机分配IP192.168.50.5
  • 为虚拟机设置主机名
  • 升级Linux包
  • 为服务器配置本地化设置
  • 安装所有Linux依赖,包括Python、Apache、PostgreSQL、Postfix和Memcached等
  • 为Django项目创建一个Linux用户和家目录
  • 为Django项目创建虚拟环境
  • 创建PostgreSQL数据库用户及数据库
  • 配置Apache网页服务器
  • 安装自签署SSL证书
  • 配置Memcached缓存服务
  • 配置Postfix邮件服务器
  • 克隆Django项目仓库
  • 安装Python依赖
  • 创建secrets.json文件
  • 迁移数据库
  • 转存静态文件
  • 重启Apache

现在可通过https://www.myproject.192.168.50.5.xip.io访问Django网站,显示Hello, World!页。注意有些浏览器,如Chrome,可能不希望打开自签署SSL证书的网站,并采取屏蔽打开的安全措施。

💡xip.io是一种通配DNS服务,将指定 IP的子域名指向某一IP,并允许使用SSL证书或其它需要使用域名的网站功能。

如果希望使用不同的配置或更多命令进行验证,合理的方式是一点点增加修改。有些部分在将任务转化为Ansible指令之前需要直接在虚拟机上进行测试。

💡有关Ansible的相关信息,可以查看其官方网站。它展示了很多用例的有用示例。

如果任何服务报错,ssh连接到虚拟机,切换为root用户,查看该服务的日志。Google 错误系统可以让我们的系统更快正常运行。

使用如下命令重建虚拟机:

$ vagrant destroy
$ vagrant up --provision

相关内容

在生产环境通过mod_wsgi部署Apache

Apache是最流行的web服务器之一。对于在同一服务器上存在要求Apache的服务器管理、监控、分析、博客、电商等时,使用Apache来部署Django是很自然的选择。

本节中,我们将继续使用前一小节的内容,实现一个Ansible脚本(playbook)来在生产环境配置带有mod_wsgi模块的Apache。

准备工作

确保在运行如下命令时项目配置不会抛出警告:

(env)$ python manage.py check --deploy --settings=myproject.settings.production

请安装最新稳定版的Ansible。

选择一个服务器提供商创建一个独立服务器,可使用公钥及私钥认证通过SSH进行 root 访问。我选择的提供商是DigitalOcean,通过它我创建了一个Ubuntu 18系统的独立服务器(Droplet)。我可以使用新的SSH私钥和公钥对通过 IP 142.93.167.30连接到服务器,分别为~/.ssh/id_rsa_django_cookbook 和 ~/.ssh/id_rsa_django_cookbook.pub。

本地我们需要按照如下内容创建或修改 ~/.ssh/config文件来配置SSH连接:

# ~/.ssh/config
Host *
   ServerAliveInterval 240
   AddKeysToAgent yes
   UseKeychain yes
       
Host github
    Hostname github.com
    IdentityFile ~/.ssh/id_rsa_github

Host myproject-apache
    Hostname 142.93.167.30
    User root
    IdentityFile ~/.ssh/id_rsa_django_cookbook

现在我们应该可以使用如下命令通过SSH以root用户连接到这台独立主机:

$ ssh myproject-apache

在域名配置中,将域名的DNS A记录指向独立服务器的IP地址。配合中,我们使用myproject.142.93.167.30.xip.io来展示如何为Django站点配置SSL证书。

ℹ️前面提到过,xip.io是一个通配DNS服务,它将具体IP的子域名指向IP并可以用它添加SSL证书或其它需要用到域名的网站功能。

如何实现...

执行如下步骤为生产环境创建部署脚本:

  1. 确保有前面在预发布环境通过mod_wsgi部署Apache一节中创建的 deployment/ansible_templates及用于服务配置的Jinja模板。

  2. 为Ansible脚本创建deployment/production 和 deployment/production/ansible目录。

  3. 在其中创建一个hosts目录以及包含如下内容的remote文件:

    # deployment/production/ansible/hosts/remote
    [servers] 
    myproject-apache
    
    [servers:vars] 
    ansible_python_interpreter=/usr/bin/python3
    
  4. 创建一个vars.yml 文件,其中包含安装脚本和配置的Jinja模板所使用的变量:

    # deployment/production/ansible/vars.yml
    ---
    # a unix path-friendly name (IE, no spaces or special characters) 
    project_name: myproject
               
    user_username: "{{ project_name }}"
    # the base path to install to. You should not need to change this. 
    install_root: /home
    
    project_root: "{{ install_root }}/{{ project_name }}"
    
    # the python module path to your project's wsgi file 
    wsgi_module: myproject.wsgi
    
    # any directories that need to be added to the PYTHONPATH. 
    python_path: "{{ project_root }}/src/{{ project_name }}"
    
    # the git repository URL for the project
    project_repo: [email protected]:archatas/django-myproject.git
    
    # The value of your django project's STATIC_ROOT settings. 
    static_root: "{{ python_path }}/static"
    media_root: "{{ python_path }}/media"
    
    locale: en_US.UTF-8 
    timezone: Europe/Berlin
    
    domain_name: myproject.142.93.167.30.xip.io 
    django_settings: myproject.settings.production
    
    # letsencrypt settings 
    letsencrypt_email: [email protected] 
    wsgi_file_name: wsgi_production.py
    
  5. 同时我们需要一个包含密码及认证密钥等私密值的secrets.yml文件。首先创建一个不包含敏感信息只具有变量名的sample_secrets.yml文件,然后拷贝到secrets.yml并填写私密信息。前一个文件进行版本控制,后一个进行忽略:

    # deployment/production/ansible/sample_secrets.yml
    # Django Secret Key
    django_secret_key: "change-this-to-50-characters-long-random-string"
    
    # PostgreSQL database settings
    db_name: "myproject"
    db_user: "myproject"
    db_password: "change-this-to-a-secret-password" 
    db_host: "localhost"
    db_port: "5432"
    
    # Email SMTP settings
    email_host: "localhost"
    email_port: "25"
    email_host_user: ""
    email_host_password: ""
    
    # a private key that has access to the repository URL 
    ssh_github_key: ~/.ssh/id_rsa_github
    
  6. 创建一个Ansible脚本(playbook)deployment/production/ansible/setup.yml,用于安装所有依赖及配置服务。通过https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-apache/production/ansible/setup.yml复制这一文件的内容。

  7. 然后创建另一个Ansible脚本deployment/production/ansible/deploy.yml,用于处理Django项目,通过https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-apache/production/ansible/deploy.yml复制这一文件的内容。

  8. 创建一个可以执行来启动部署的bash脚本:

    # deployment/production/ansible/setup_remotely.sh
    #!/usr/bin/env bash
    echo "=== Setting up the production server ===" 
    date
    
    cd "$(dirname "$0")"
    ansible-playbook setup.yml -i hosts/remote
    
  9. 为bash脚本添加执行权限并运行:

    $ chmod +x setup_remotely.sh 
    $ ./setup_remotely.sh
    
  10. 如果脚本失败报错,很可能需要重启独立主机来让修改生效。可以通过ssh连接到服务器上用下面的命令重启:

    $ ssh myproject-apache
    Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-74-generic x86_64)
    
      * Documentation: https://help.ubuntu.com
      * Management: https://landscape.canonical.com 
      * Support: https://ubuntu.com/advantage
       
       System information as of Wed Jan 15 11:39:51 CET 2020
    
      System load: 0.08 Processes: 104
      Usage of /: 8.7% of 24.06GB Users logged in: 0 
      Memory usage: 35% IP address for eth0: 142.93.167.30 
      Swap usage: 0%
      
      * Canonical Livepatch is available for installation.
      - Reduce system reboots and improve kernel security. Activate at: https://ubuntu.com/livepatch
    
    0 packages can be updated.
    0 updates are security updates.
    
    *** System restart required ***
    
    Last login: Sun Jan 12 12:23:35 2020 from 178.12.115.146 
    root@myproject:~# reboot
    Connection to 142.93.167.30 closed by remote host. 
    Connection to 142.93.167.30 closed.
    
  11. 创建另一个用于更新Django项目的bash脚本:

    # deployment/production/ansible/deploy_remotely.sh
    #!/usr/bin/env bash
    echo "=== Deploying project to production server ===" 
    date
    
    cd "$(dirname "$0")"
    ansible-playbook deploy.yml -i hosts/remote
    
  12. 为这个bash脚本添加执行权限:

    $ chmod +x deploy_remotely.sh
    

实现原理...

Ansible脚本(playbook)是幂等的。即你可以执行多次但永远得到同样的结果:一个安装并运行了Django网站的最新版独立主机。如果服务器有任何技术硬件问题并且有数据库和媒体文件备份的话,可以相对快速地在另一台独立主机上以同样的配置进行安装。

生产部署脚本执行如下操作:

  • 为虚拟机设置主机名
  • 升级Linux包
  • 为服务器配置本地化设置
  • 安装包括Python、Apache、PostgreSQL、Postfix、Memcached等的Linux依赖
  • 创建一个Linux用户以及用于Django项目的home目录
  • 为Django项目创建一个虚拟环境
  • 创建PostgreSQL数据库用户及数据库
  • 配置Apache网页服务器
  • 安装Let's Encrypt SSL证书
  • 配置Memcached缓存服务
  • 配置Postfix邮件服务器
  • 克隆Django项目仓库
  • 安装Python依赖
  • 创建secrets.json文件
  • 迁移数据库
  • 转存静态文件
  • 重启Apache

在首次需要安装服务及依赖时运行setup_remotely.sh脚本。然后,如果需要升级Django项目的话可以使用deploy_remotely.sh。可以看到,这里的安装非常类似于预发布服务器,但为保持灵活以及更利于修改,我们将其单独保存到了deployment/production目录中。

理论上你可以完全跳过预发布环境,便尝试在虚拟机上进行部署流程比直接在远程服务上进行安装试验来得更切实际。

相关内容

在预发布环境中基于Nginx和Gunicorn部署一节

Apache和mod_wsgi是一种良好稳定的部署方式,但在需要高性能的时候,推荐使用Nginx和Gunicorn来对Django网站提供服务。Gunicorn是一种Python服务端运行的WSGI脚本。Nginx是一个解析域名配置并将请求传递给Gunicorn的web服务器。

本节中,我们将展示如何创建一个脚本来部署项目到电脑虚拟机上的预发布环境中。实现部署,我们会使用Ansible、Vagrant和VirtualBox。前面提到过,很多细节要铭记于心,并且需要假以时日来开发类似于这里的优化开发脚本。

准备工作

按照https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/上的部署清单,确保配置通过所有的安全推荐。至少应保证在运行如下命令时项目配置不会抛出任务警告:

(env)$ python manage.py check --deploy --settings=myproject.settings.staging

安装最新的稳定版Ansible、Vagrant和VirtualBox。可通过官方网站获取到这些软件:

在macOS上可通过HomeBrew进行安装:

$ brew install ansible
$ brew cask install virtualbox
$ brew cask install vagrant

如何实现...

首先,我们需要创建一些服务器上使用的各类服务的配置模板。由两个部署流程使用:预发布和生产。

  1. 在Django项目中,创建一个deployment目录并在其中创建一个ansible_templates目录。

  2. 为时区配置创建一个Jinja模板:

    {# deployment/ansible_templates/timezone.j2 #}
    {{ timezone }}
    
  3. 在设置SSL证书前为Nginx域名配置创建一个Jinja模板:

    {# deployment/ansible_templates/nginx-pre.j2 #}
    server{
      listen 80;
      server_name {{ domain_name }};
    
      location /.well-known/acme-challenge { 
        root /var/www/letsencrypt; 
        try_files $uri $uri/ =404;
      }
      location / {
        root /var/www/letsencrypt;
      }
    }
    
  4. 在deployment/ansible_templates/nginx.j2 中为Nginx配置包括SSL证书创建Jinja模板。从https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-nginx/ansible_templates/nginx.j2拷贝内容到这个文件中。

  5. 为Gunicorn服务配置创建一个模板:

    # deployment/ansible_templates/gunicorn.j2
    [Unit]
    Description=Gunicorn daemon for myproject website 
    After=network.target
    
    [Service]
    PIDFile=/run/gunicorn/pid
    Type=simple
    User={{ user_username }}
    Group=www-data
    RuntimeDirectory=gunicorn
    WorkingDirectory={{ python_path }}
    ExecStart={{ project_root }}/env/bin/gunicorn --pid /run/gunicorn/pid --log-file={{ project_root }}/logs/gunicorn.log --workers {{ ansible_processor_count | int }} --bind 127.0.0.1:8000 {{ project_name }}.wsgi:application --env DJANGO_SETTINGS_MODULE={{ django_settings }} --max-requests 1000
    ExecReload=/bin/kill -s HUP $MAINPID
    ExecStop=/bin/kill -s TERM $MAINPID
    PrivateTmp=true
    
    [Install]
    WantedBy=multi-user.target
    
  6. 在deployment/ansible_templates/postgresql.j2 中为PostgreSQL配置文件创建一个模板,使用内容https://github.com/postgres/postgres/blob/REL_10_STABLE/src/backend/utils/misc/postgresql.conf.sample。稍后可以在这个文件中修改配置。

  7. 为PostgreSQL权限配置文件创建一个模板(当前授予权限较大,但可以根据需要进行修改):

    {# deployment/ansible_templates/pg_hba.j2 #}
    # TYPE  DATABASE        USER           CIDR-ADDRESS    METHOD
    local   all             all                            ident
    host    all             all            ::0/0           md5
    host    all             all            0.0.0.0/32      md5
    host    {{ db_name }}   {{ db_user }}  127.0.0.1/32    md5
    
  8. 为Postfix邮件服务器配置创建一个模板:

    {# deployment/ansible_templates/postfix.j2 #}
    # See /usr/share/postfix/main.cf.dist for a commented, more 
    # complete version
    
    
    # Debian specific:  Specifying a file name will cause the first
    # line of that file to be used as the name.  The Debian default
    # is /etc/mailname.
    # myorigin = /etc/mailname
    
    smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
    biff = no
    
    # appending .domain is the MUA's job.
    append_dot_mydomain = no
    
    # Uncomment the next line to generate "delayed mail" warnings
    #delay_warning_time = 4h
    
    readme_directory = no
    
    # TLS parameters
    smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
    smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
    smtpd_use_tls=yes
    smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
    smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
    
    # See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc 
    # package for information on enabling SSL 
    # in the smtp client.
    
    smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
    myhostname = {{ domain_name }}
    alias_maps = hash:/etc/aliases
    alias_database = hash:/etc/aliases
    mydestination = $myhostname, localhost, localhost.localdomain, , 
     localhost
    relayhost =
    mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
    mailbox_size_limit = 0
    recipient_delimiter = +
    inet_interfaces = all
    inet_protocols = all
    virtual_alias_domains = {{ domain_name }}
    virtual_alias_maps = hash:/etc/postfix/virtual
    
  9. 为邮件转发配置创建一个模板:

    {# deployment/ansible_templates/virtual.j2 #}
    # /etc/postfix/virtual
    
    hello@{{ domain_name }} [email protected] 
    @{{ domain_name }} [email protected]
    
  10. 为memcached配置创建一个模板:

    {# deployment/ansible_templates/memcached.j2 #}
    # memcached default config file
    # 2003 - Jay Bonci <[email protected]>
    # This configuration file is read by the start-memcached script 
    # provided as part of the Debian GNU/Linux distribution.
    
    # Run memcached as a daemon. This command is implied, and is not 
    # needed for the daemon to run. See the README.Debian 
    # that comes with this package for more information.
    -d
    
    # Log memcached's output to /var/log/memcached
    logfile /var/log/memcached.log
    
    # Be verbose
    # -v
    
    # Be even more verbose (print client commands as well)
    # -vv
    
    # Use 1/16 of server RAM for memcached
    -m {{ (ansible_memtotal_mb * 0.0625) | int }}
    
    # Default connection port is 11211
    -p 11211
    
    # Run the daemon as root. The start-memcached will default to 
    # running as root if no -u command is present 
    # in this config file
    -u memcache
    
    # Specify which IP address to listen on. The default is to 
    # listen on all IP addresses
    # This parameter is one of the only security measures that 
    # memcached has, so make sure it's listening 
    # on a firewalled interface.
    -l 127.0.0.1
    
    # Limit the number of simultaneous incoming connections. The 
    # daemon default is 1024
    # -c 1024
    
    # Lock down all paged memory. Consult with the README and homepage 
    # before you do this
    # -k
    
    # Return error when memory is exhausted (rather than 
    # removing items)
    # -M
    
    # Maximize core file limit
    # -r
    
  11. 最后,为secrets.json文件创建一个Jinja模板:

    {# deployment/ansible_templates/secrets.json.j2 #}
    {
        "DJANGO_SECRET_KEY": "{{ django_secret_key }}",
        "DATABASE_ENGINE": "django.contrib.gis.db.backends.postgis",
        "DATABASE_NAME": "{{ db_name }}",
        "DATABASE_USER": "{{ db_user }}",
        "DATABASE_PASSWORD": "{{ db_password }}",
        "EMAIL_HOST": "{{ email_host }}",
        "EMAIL_PORT": "{{ email_port }}",
        "EMAIL_HOST_USER": "{{ email_host_user }}",
        "EMAIL_HOST_PASSWORD": "{{ email_host_password }}"
    }
    

下面编写预发布环境所需使用的Vagrant和Ansible脚本:

  1. 在 .gitignore文件中,添加如下行来忽略掉一些Vagrant和Ansible相关文件:

    # .gitignore
    # Secrets
    secrets.json
    secrets.yml
    
    # Vagrant / Ansible
    .vagrant
    *.retry
    
  2. 创建deployment/staging 及 deployment/staging/ansible 目录。

  3. 在deployment/staging/ansible目录中,创建一个带有如下脚本的Vagrantfile文件,用于创建一个系统为Ubuntu 18的虚拟机并在其上运行Ansible脚本:

    # deployment/staging/ansible/Vagrantfile
    VAGRANTFILE_API_VERSION = "2"
    
    Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
      config.vm.box = "bento/ubuntu-18.04"
      config.vm.box_version = "201912.14.0"
      config.vm.box_check_update = false
      config.ssh.insert_key=false
      config.vm.provider "virtualbox" do |v|
        v.memory = 512
        v.cpus = 1
        v.name = "myproject"
      end
      config.vm.network "private_network", ip: "192.168.50.5"
      config.vm.provision "ansible" do |ansible|
        ansible.limit = "all"
        ansible.playbook = "setup.yml"
        ansible.inventory_path = "./hosts/vagrant"
        ansible.host_key_checking = false
        ansible.verbose = "vv"
        ansible.extra_vars = { ansible_python_interpreter: 
        "/usr/bin/python3" }
      end
    end
    
  4. 创建一个hosts目录及包含如下内容的vagrant文件:

    # deployment/staging/ansible/hosts/vagrant
    [servers]
    192.168.50.5
    
  5. 创建一个vars.yml 文件,其中包含在安装脚本和用于配置的Jinja模板所使用的模板:

    # deployment/staging/ansible/vars.yml
    ---
    # a unix path-friendly name (IE, no spaces or special characters)
    project_name: myproject
    
    user_username: "{{ project_name }}"
    
    # the base path to install to. You should not need to change this.
    install_root: /home
    
    project_root: "{{ install_root }}/{{ project_name }}"
    
    # the python module path to your project's wsgi file
    wsgi_module: myproject.wsgi
    
    # any directories that need to be added to the PYTHONPATH.
    python_path: "{{ project_root }}/src/{{ project_name }}"
    
    # the git repository URL for the project
    project_repo: [email protected]:archatas/django-myproject.git
    
    # The value of your django project's STATIC_ROOT settings.
    static_root: "{{ python_path }}/static"
    media_root: "{{ python_path }}/media"
    
    locale: en_US.UTF-8
    timezone: Europe/Berlin
    
    domain_name: myproject.192.168.50.5.xip.io
    django_settings: myproject.settings.staging
    
    letsencrypt_email: ""
    
  6. 我们还需要一个包含密码和认证密钥等私密值的secrets.yml文件。首先,创建一个不含敏感信息仅包含变量名的sample_secrets.yml文件,然后将其拷贝到secrets.yml并填写保密信息。前一个文件进行版本控制,后者进行忽略:

    # deployment/staging/ansible/sample_secrets.yml
    # Django Secret Key
    django_secret_key: "change-this-to-50-characters-long-random-string"
    
    # PostgreSQL database settings
    db_name: "myproject"
    db_user: "myproject"
    db_password: "change-this-to-a-secret-password"
    db_host: "localhost"
    db_port: "5432"
    
    # Email SMTP settings
    email_host: "localhost"
    email_port: "25"
    email_host_user: ""
    email_host_password: ""
    
    # a private key that has access to the repository URL
    ssh_github_key: ~/.ssh/id_rsa_github
    
  7. 在deployment/staging/ansible/setup.yml 处创建一个Ansible脚本(playbook)用于安装所有依赖及配置服务。从https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-nginx/staging/ansible/setup.yml拷贝入文件内容。

  8. 然后在deployment/staging/ansible/deploy.yml处创建另一个Ansible脚本处理Django项目。从https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-nginx/staging/ansible/deploy.yml拷入文件内容。

  9. 创建一个可以执行启动部署的bash脚本:

    # deployment/staging/ansible/setup_on_virtualbox.sh
    #!/usr/bin/env bash
    echo "=== Setting up the local staging server ==="
    date
    
    cd "$(dirname "$0")"
    vagrant up --provision
    
  10. 为bash脚本添加执行执行并运行:

    $ chmod +x setup_on_virtualbox.sh
    $ ./setup_on_virtualbox.sh
    
  11. 如果脚本失败报错,很可能需要重启虚拟机来让修改生效。可以通过ssh连接到虚拟机,切换为root,按照下面这样进行重启:

    $ vagrant ssh
    Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-72-generic x86_64)
    
     * Documentation:  https://help.ubuntu.com
     * Management:     https://landscape.canonical.com
     * Support:        https://ubuntu.com/advantage
    
      System information as of Wed Jan 15 04:44:42 CET 2020
    
      System load:  0.21              Processes:           126
      Usage of /:   4.0% of 61.80GB   Users logged in:     1
      Memory usage: 35%               IP address for eth0: 10.0.2.15
      Swap usage:   4%                IP address for eth1: 192.168.50.5
    
    
    0 packages can be updated.
    0 updates are security updates.
    
    
    *** System restart required ***
    
    This system is built by the Bento project by Chef Software
    More information can be found at https://github.com/chef/bento
    Last login: Wed Jan 15 04:43:32 2020 from 192.168.50.1
    vagrant@myproject:~$ sudo su
    root@myproject:/home/vagrant#
    reboot
    Connection to 127.0.0.1 closed by remote host.
    Connection to 127.0.0.1 closed.
    
  12. 要浏览Django项目目录,ssh连接至虚拟机并像下面这样切换用户为myproject:

    $ vagrant ssh
    Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-74-generic x86_64)
    # … 
    vagrant@myproject:~$ sudo su - myproject
    (env) myproject@myproject:~$ pwd
    /home/myproject
    (env) myproject@myproject:~$ ls
    commands db_backups logs public_html src env
    

实现原理...

VirtualBox让我们可以在同一台电脑上安装不同操作系统的多个虚拟主机。Vagrant是一个创建这些虚拟主机的工具,我们可以在上面下载并安装操作系统。Ansible是一个基于Python的工具,它读取.yaml 配置文件并在远程主机上进行执行。

我们刚刚编写的部署脚本执行如下操作:

  • 在VirtualBox中创建虚拟主机并安装Ubuntu 18
  • 为虚拟主机分配IP地址192.168.50.5
  • 为虚拟主机设置主机名
  • 升级Linux包
  • 为服务器设置本地化配置
  • 安装所有Linux依赖,包括Python、Nginx、PostgreSQL、Postfix、Memcached等。
  • 为Django项目创建一个Linux用户和家目录
  • 为Django项目创建一个虚拟环境
  • 创建PostgreSQL数据库用户及数据库
  • 配置Nginx网页服务器
  • 安装自签署SSL证书
  • 配置Memcached缓存服务
  • 配置Postfix邮件服务器
  • 克隆Django项目仓库
  • 安装Python依赖
  • 配置Gunicorn
  • 创建secrets.json文件
  • 迁移数据库
  • 转存静态文件
  • 重启Nginx

现在可通过https://www.myproject.192.168.50.5.xip.io访问这个Django网站,它会显示一个Hello, World!页面。注意有些浏览器包括Chrome在内可能会不允许打开自签署SSL证书的网站,出于安全措施进行了屏蔽。

ℹ️xip.io是一个通配符DNS服务,它将具体IP子域名指向该IP,并让我们可以使用来安装SSL证书或其它需要域名的网站功能。

如果希望测试其它配置或更多的命令,小步增量修改是一种合理的选择。对于有些部分,会需要在将任务转化为Ansible指令之前直接在虚拟机上测试。

💡更多有关Ansible的使用信息,请查看官方网站。它显示了针对大量用例的有用指导示例。

如果任何服务报错了,ssh连接至虚拟机,切换为root用户,查看该服务的日志。Google 该错误信息会让们更接近一个有效系统。

使用如下命令重建虚拟机:

$ vagrant destroy
$ vagrant up --provision

相关内容

在生产环境中基于Nginx和Gunicorn部署

本节中,我们将继续前一小节的操作,实现一个Ansible脚本(playbook)来使用Nginx和Gunicorn设置生产环境。

准备工作

检测在运行如下命令时项目配置不会抛出警告:

(env)$ python manage.py check --deploy --settings=myproject.settings.production

确保安装了最新稳定版的Ansible。

选择一个服务器提供商并创建一台独立服务器,可使用公钥及私钥认证通过SSH进行 root 访问。我选择的提供商是DigitalOcean,通过它我创建了一个Ubuntu 18系统的独立服务器(Droplet)。我可以使用新的SSH私钥和公钥对通过 IP 46.101.136.102连接到服务器,分别为~/.ssh/id_rsa_django_cookbook 和 ~/.ssh/id_rsa_django_cookbook.pub。

本地我们需要按照如下内容创建或修改 ~/.ssh/config文件来配置SSH连接:

# ~/.ssh/config
Host *
   ServerAliveInterval 240
   AddKeysToAgent yes
   UseKeychain yes
       
Host github
    Hostname github.com
    IdentityFile ~/.ssh/id_rsa_github

Host myproject-nginx
    Hostname 46.101.136.102
    User root
    IdentityFile ~/.ssh/id_rsa_django_cookbook

现在我们应该可以使用如下命令通过SSH以root用户连接到这台独立主机:

$ ssh myproject-nginx

在域名配置中,将域名的DNS A记录指向独立服务器的IP地址。配合中,我们使用myproject.46.101.136.102.xip.io来展示如何为Django站点配置SSL证书。

如何实现...

执行如下步骤为生产环境创建一个部署脚本:

  1. 确保有一个deployment/ansible_templates目录,其中包含有前一小节在预发布环境中基于Nginx和Gunicorn部署中所创建用于服务配置的那些Jinja模板。

  2. 为Ansible脚本创建deployment/production 和 deployment/production/ansible目录。

  3. 创建一个hosts目录及包含如下内容的remote文件:

    # deployment/production/ansible/hosts/remote
    [servers] 
    myproject-nginx
    
    [servers:vars] 
    ansible_python_interpreter=/usr/bin/python3
    
  4. 创建一个vars.yml文件,其中包含在安装脚本和用于配置的Jinja模板中使用的变量:

    # deployment/production/ansible/vars.yml
    ---
    # a unix path-friendly name (IE, no spaces or special characters)
    project_name: myproject
    
    user_username: "{{ project_name }}"
    
    # the base path to install to. You should not need to change this.
    install_root: /home
    
    project_root: "{{ install_root }}/{{ project_name }}"
    
    # the python module path to your project's wsgi file
    wsgi_module: myproject.wsgi
    
    # any directories that need to be added to the PYTHONPATH.
    python_path: "{{ project_root }}/src/{{ project_name }}"
    
    # the git repository URL for the project
    project_repo: [email protected]:archatas/django-myproject.git
    
    # The value of your django project's STATIC_ROOT settings.
    static_root: "{{ python_path }}/static"
    media_root: "{{ python_path }}/media"
    
    locale: en_US.UTF-8
    timezone: Europe/Berlin
    
    domain_name: myproject.46.101.136.102.xip.io
    django_settings: myproject.settings.production
    
    # letsencrypt settings
    letsencrypt_email: [email protected]
    
  5. 我们还需要一个包含密码和认证密钥等保密信息的secrets.yml文件。首先,创建一个不包含敏感信息仅包含变量名的sample_secrets.yml文件,然后将其拷贝到secrets.yml,填入保密信息。前一个文件进行版本控制,后者进行忽略:

    # deployment/production/ansible/sample_secrets.yml
    # Django Secret Key
    django_secret_key: "change-this-to-50-characters-long-random-string"
    
    # PostgreSQL database settings
    db_name: "myproject"
    db_user: "myproject"
    db_password: "change-this-to-a-secret-password"
    db_host: "localhost"
    db_port: "5432"
    
    # Email SMTP settings
    email_host: "localhost"
    email_port: "25"
    email_host_user: ""
    email_host_password: ""
    
    # a private key that has access to the repository URL
    ssh_github_key: ~/.ssh/id_rsa_github
    
  6. 现在可以在deployment/production/ansible/setup.yml处创建一个Ansible脚本(playbook),用于安装所有依赖和配置服务。从https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-nginx/production/ansible/setup.yml拷入该文件的内容。

  7. 然后创建另一个Ansible脚本deployment/production/ansible/deploy.yml 来处理Django项目。从https://raw.githubusercontent.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/master/ch12/myproject_virtualenv/src/django-myproject/deployment-nginx/production/ansible/deploy.yml拷入文件的内容。

  8. 创建一个可以执行启动部署的bash脚本:

    # deployment/production/ansible/setup_remotely.sh
    #!/usr/bin/env bash
    echo "=== Setting up the production server ==="
    date
    
    cd "$(dirname "$0")"
    ansible-playbook setup.yml -i hosts/remote
    
  9. 为bash脚本添加执行权限并运行:

    $ chmod +x setup_remotely.sh
    $ ./setup_remotely.sh
    
  10. 如果脚本失败报错,很可能需要重启独立主机来使用修改生效。可以通过ssh连接到服务器并像如下这样进行重启:

    $ ssh myproject-nginx
    Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-74-generic x86_64)
    
     * Documentation: https://help.ubuntu.com
     * Management: https://landscape.canonical.com
     * Support: https://ubuntu.com/advantage
    
     System information as of Wed Jan 15 11:39:51 CET 2020
    
     System load: 0.08 Processes: 104
     Usage of /: 8.7% of 24.06GB Users logged in: 0
     Memory usage: 35% IP address for eth0: 142.93.167.30
     Swap usage: 0%
    
     * Canonical Livepatch is available for installation.
     - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch
    
    0 packages can be updated.
    0 updates are security updates.
    
    
    *** System restart required ***
    
    Last login: Sun Jan 12 12:23:35 2020 from 178.12.115.146
    root@myproject:~# reboot
    Connection to 142.93.167.30 closed by remote host.
    Connection to 142.93.167.30 closed.
    
  11. 创建另一个bash脚本仅用于更新Django项目:

    # deployment/production/ansible/deploy_remotely.sh
    #!/usr/bin/env bash
    echo "=== Deploying project to production server ==="
    date
    
    cd "$(dirname "$0")"
    ansible-playbook deploy.yml -i hosts/remote
    
  12. 为bash脚本添加执行权限:

    $ chmod +x deploy_remotely.sh
    

实现原理...

Ansible脚本(playbook)是幂等的。即执行多次仍将得到相同的结果,一个安装并运行了Django网站的最新版独立主机。如果服务器出现硬件问题,并且有数据库和媒体文件的备份,可以相对快速地在另一台独立服务器上安装相同的配置。

生产部署脚本执行如下操作:

  • 为虚拟机设置主机名
  • 升级Linux包
  • 为服务器设置本地化配置
  • 安装Python、Nginx、PostgreSQL、Postfix、Memcached等Linux依赖
  • 为Django项目创建一个Linux用户和home目录
  • 为Django项目创建一个虚拟环境
  • 创建PostgreSQL数据库用户及数据库
  • 配置Nginx网页服务器
  • 安装Let's Encrypt SSL证书
  • 配置Memcached缓存服务
  • 配置Postfix邮件服务器
  • 克隆Django项目仓库
  • 安装Python依赖
  • 配置Gunicorn
  • 创建secrets.json文件
  • 迁移数据库
  • 转存静态文件
  • 重启Nginx

可以看到,这里的安装非常类似预发布服务器,但为保持灵活和易于修改,我们将其单独保存到了deployment/production 目录中。

理论上说,可以完全跳过预发布环境,但在虚拟机上测试部署环节比直接在远程服务器上进行安装试验要更切实际。

相关内容