iT邦幫忙

2021 iThome 鐵人賽

DAY 17
0
DevOps

關於我幫新公司建立整套部屬流程那檔事系列 第 17

EP17 - 容器化你的 Django 專案

昨天我們簡單介紹什麼是容器,
今天我們要開始實戰,
將你的 Django Portal,
逐步包裝成容器,
並放到 AWS 的容器儲存庫(ECR),
中間的過程,
我們會再把之前建置環境的過程簡略帶過,
建置完成後會將建置完成的容器,
在 CI/CD 的過程中,
將容器推送到 AWS 容器儲存庫(ECR)。
(還不會部署到 Kubernetes)

建立 AWS ECR使用 Terraform

什麼是 AWS ECR

Amazon Elastic Container Registry (Amazon ECR) 是一個全受管容器登錄檔
可隨時隨地輕鬆存放、管理、共享及部署容器映像和成品

Amazon ECR 讓您不必操作自己的容器儲存庫
也不必擔心如何擴展基礎設施

Amazon ECR 會將您的映像託管在高可用性及高效能架構中
讓您可靠地為您的容器應用程式部署映像

您可以在組織內以私有方式共享容器軟體
也可以在世界範圍內以公有方式共享
讓任何人都可以探索和下載
所以使用容器儲存庫
不一定要使用 Dockerhub
使用 Amazon ECR 一樣可公開可私有

Terraform

不要懷疑
建立一個 ecr repository 就是這麼簡單
比較特別的是 image_tag_mutability 這個 attribute
因為我們在 push 上 repository 的時候
會不斷地覆蓋 latest
如果沒有設成 MUTABLE
會造成第一次可以順利 push
之後 push latest 的時候因為不能更改
所以就失敗了

resource "aws_ecr_repository" "portal" {
    name                 = "portal"
    image_tag_mutability = "MUTABLE"
    
    image_scanning_configuration {
        scan_on_push = true
    }
}
terraform apply

AWS 查看

建立後我們可以登入 AWS Cloud Console
查看是否有正常建立

在搜尋框內輸入 ECR
https://ithelp.ithome.com.tw/upload/images/20210929/20141518EXKsTwwlg8.png

點入後就可以看見容器儲存庫的列表
https://ithelp.ithome.com.tw/upload/images/20210929/20141518dO7mVt1t6A.png

將 Django 專案容器化

安裝 Docker

EP10 - Django 持續整合持續部署使用 Jenkins 和 AWS CodeDeploy 的時候
我們有介紹過如何在 Jenkins Server 上安裝 Docker
這次我們要安裝的環境是本機的 vagrant
因為 Ubuntu 版本相同
所以也比照同樣方式安裝就可以了
不過因為我們沒有在本機跑 Jenkins
所以最後 Grant Jenkins 的權限就不用做
只需要 Grant 給 vagrant 就可以了

那我們先到我們虛擬機械的 console

Adding Docker’s GPG Key

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

Installing the Docker Repository

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu  $(lsb_release -cs)  stable"

Installing the Latest Docker

sudo apt update
sudo apt-get install docker-ce

Verifying Docker Installation

docker --version

Start and Enable Docker

sudo systemctl start docker
sudo systemctl enable docker

變更權限

sudo chown vagrant:docker /var/run/docker.sock
sudo chmod 644 /var/run/docker.sock

修改 apache.conf

上次我們在 EP09 - 建立 Django 專案和 EC2 環境 並手動部署到 EC2
有提到要去 /etc/apache2/apache2.conf 的最下方 apend 幾行
但是要實際建立 Docker 就不能這麼偷懶
這裡我是會把 apache.conf 進版控
並且在製作容器時
整份檔案複製進去

我們在 portal 專案的根目錄新增一個 apache2.conf 檔案
並把以下檔案附近進去
需要注意的是 LoadModule wsgi_module 的檔名和位置
會因為使用的 python 版本而有所不同

# This is the main Apache server configuration file.  It contains the
# configuration directives that give the server its instructions.
# See http://httpd.apache.org/docs/2.4/ for detailed information about
# the directives and /usr/share/doc/apache2/README.Debian about Debian specific
# hints.
#
#
# Summary of how the Apache 2 configuration works in Debian:
# The Apache 2 web server configuration in Debian is quite different to
# upstream's suggested way to configure the web server. This is because Debian's
# default Apache2 installation attempts to make adding and removing modules,
# virtual hosts, and extra configuration directives as flexible as possible, in
# order to make automating the changes and administering the server as easy as
# possible.

# It is split into several files forming the configuration hierarchy outlined
# below, all located in the /etc/apache2/ directory:
#
#       /etc/apache2/
#       |-- apache2.conf
#       |       `--  ports.conf
#       |-- mods-enabled
#       |       |-- *.load
#       |       `-- *.conf
#       |-- conf-enabled
#       |       `-- *.conf
#       `-- sites-enabled
#               `-- *.conf
#
#
# * apache2.conf is the main configuration file (this file). It puts the pieces
#   together by including all remaining configuration files when starting up the
#   web server.
#
# * ports.conf is always included from the main configuration file. It is
#   supposed to determine listening ports for incoming connections which can be
#   customized anytime.
#
# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/
#   directories contain particular configuration snippets which manage modules,
#   global configuration fragments, or virtual host configurations,
#   respectively.
#
#   They are activated by symlinking available configuration files from their
#   respective *-available/ counterparts. These should be managed by using our
#   helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See
#   their respective man pages for detailed information.
#
# * The binary is called apache2. Due to the use of environment variables, in
#   the default configuration, apache2 needs to be started/stopped with
#   /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not
#   work with the default configuration.


# Global configuration
#

#
# ServerRoot: The top of the directory tree under which the server's
# configuration, error, and log files are kept.
#
# NOTE!  If you intend to place this on an NFS (or otherwise network)
# mounted filesystem then please read the Mutex documentation (available
# at <URL:http://httpd.apache.org/docs/2.4/mod/core.html#mutex>);
# you will save yourself a lot of trouble.
#
# Do NOT add a slash at the end of the directory path.
#
#ServerRoot "/etc/apache2"

#
# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
#
#Mutex file:${APACHE_LOCK_DIR} default

#
# The directory where shm and other runtime files will be stored.
#

DefaultRuntimeDir ${APACHE_RUN_DIR}

#
# PidFile: The file in which the server should record its process
# identification number when it starts.
# This needs to be set in /etc/apache2/envvars
#
PidFile ${APACHE_PID_FILE}

#
# Timeout: The number of seconds before receives and sends time out.
#
Timeout 300

#
# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
#
KeepAlive On

#
# MaxKeepAliveRequests: The maximum number of requests to allow
# during a persistent connection. Set to 0 to allow an unlimited amount.
# We recommend you leave this number high, for maximum performance.
#
MaxKeepAliveRequests 100

#
# KeepAliveTimeout: Number of seconds to wait for the next request from the
# same client on the same connection.
#
KeepAliveTimeout 5


# These need to be set in /etc/apache2/envvars
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}

#
# HostnameLookups: Log the names of clients or just their IP addresses
# e.g., www.apache.org (on) or 204.62.129.132 (off).
# The default is off because it'd be overall better for the net if people
# had to knowingly turn this feature on, since enabling it means that
# each client request will result in AT LEAST one lookup request to the
# nameserver.
#
HostnameLookups Off

# ErrorLog: The location of the error log file.
# If you do not specify an ErrorLog directive within a <VirtualHost>
# container, error messages relating to that virtual host will be
# logged here.  If you *do* define an error logfile for a <VirtualHost>
# container, that host's errors will be logged there and not here.
#
ErrorLog ${APACHE_LOG_DIR}/error.log

#
# LogLevel: Control the severity of messages logged to the error_log.
# Available values: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the log level for particular modules, e.g.
# "LogLevel info ssl:warn"
#
LogLevel warn

# Include module configuration:
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf

# Include list of ports to listen on
Include ports.conf


# Sets the default security model of the Apache2 HTTPD server. It does
# not allow access to the root filesystem outside of /usr/share and /var/www.
# The former is used by web applications packaged in Debian,
# the latter may be used for local directories served by the web server. If
# your system is serving content from a sub-directory in /srv you must allow
# access here, or in any related virtual host.
<Directory />
        Options FollowSymLinks
        AllowOverride None
        Require all denied
</Directory>

<Directory /usr/share>
        AllowOverride None
        Require all granted
</Directory>

<Directory /var/www/>
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
</Directory>

#<Directory /srv/>
#       Options Indexes FollowSymLinks
#       AllowOverride None
#       Require all granted
#</Directory>




# AccessFileName: The name of the file to look for in each directory
# for additional configuration directives.  See also the AllowOverride
# directive.
#
AccessFileName .htaccess

#
# The following lines prevent .htaccess and .htpasswd files from being
# viewed by Web clients.
#
<FilesMatch "^\.ht">
        Require all denied
</FilesMatch>

#
# The following directives define some format nicknames for use with
# a CustomLog directive.
#
# These deviate from the Common Log Format definitions in that they use %O
# (the actual bytes sent including headers) instead of %b (the size of the
# requested file), because the latter makes it impossible to detect partial
# requests.
#
# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended.
# Use mod_remoteip instead.
#
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

# Include of directories ignores editors' and dpkg's backup files,
# see README.Debian for details.

# Include generic snippets of statements
IncludeOptional conf-enabled/*.conf

# Include the virtual host configurations:
IncludeOptional sites-enabled/*.conf

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
ServerName localhost
# WSGIDaemonProcess portal processes=2 threads=12 python-home=/var/www/portal
# WSGIProcessGroup portal
# WSGIRestrictEmbedded On
# WSGIPassAuthorization On
Alias /static/ /var/www/portal/static
<Directory /var/www/portal/static>
  Require all granted
</Directory>

LoadModule wsgi_module "/var/www/venv/portal/lib/python3.7/site-packages/mod_wsgi/server/mod_wsgi-py37.cpython-37m-x86_64-linux-gnu.so"
WSGIPythonHome /var/www/venv/portal
WSGIPythonPath /var/www/portal
WSGIScriptAlias / /var/www/portal/portal/wsgi.py
<Directory /var/www/portal/portal>
  <Files wsgi.py>
    Require all granted
  </Files>
</Directory>

配置 Dockerfile

在根目錄新增一個檔案叫 Dockerfile

FROM python:3.7.10-slim
MAINTAINER Mark_Mew

ENV APACHE_LOG_DIR /var/log/apache2

RUN apt-get -qq update \
    && apt-get upgrade -y \
    && apt-get install -y libpq-dev \
    && apt-get install -y apache2 apache2-dev \
    && apt-get clean

RUN chmod 755 -R /var/log/apache2 \
    && chown www-data:www-data -R /var/log/apache2 \
    && mkdir /var/www/portal \
    && mkdir /var/www/venv \
    && /usr/local/bin/python3.7 -m pip install --upgrade pip \
    && /usr/local/bin/pip install virtualenv \
    && /usr/local/bin/virtualenv /var/www/venv/portal

COPY apache2.conf /etc/apache2/
ADD portal.tar.gz /var/www/portal/

RUN /var/www/venv/portal/bin/pip install mod_wsgi \
   && /var/www/venv/portal/bin/pip install -r /var/www/portal/requirements.txt
   
WORKDIR /var/www/portal

CMD ["apachectl", "-D", "FOREGROUND"]

建置與執行

打包一份最新的程式碼

git archive --format=tar.gz --output ./portal.tar.gz HEAD

在專案的根目錄建置容器

sudo docker build -t portal .

執行 docker

因為 vagrant 只對外開 8000 port
但是 docker 裡面裝 apache2 預設會吃 80 port
因此需要做一次 port forward

sudo docker run -p 8000:80 -it --rm portal

打開瀏覽器確認是否正常執行

理論上上方執行無誤的話
都可以正常跑起來

其他指令

這邊主要告訴大家一條可以走的路
至於 docker 指令或 debug 方式
我覺得就有點仰賴經驗
有時候我會更改 entrypoint 用 bash 進去
看我檔案是否擺放正確
或是套件是否正常安裝

有時候則是 exec
將容器啟動並進入後將所有服務啟動
並反覆離開進入查看狀況

docker ps -a
docker rm 'container id'
docker rmi 'image id'
docker run -it --rm --entrypoint /bin/bash portal

Jenkins 整合 ECR

修改 IAM 權限

以最小權限來看
至少我們需要以下權限
因為 Container 每個聚合在一起的指令都是一層
會了能夠也將每層放上 ECR
所以有些權限也是需要加的

stage/main.tf

resource "aws_iam_user_policy" "jenkins_push_ecr" {
    name = "JenkinsPushEcr"
    user = aws_iam_user.jenkins.name
    
    policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "ecr:PutImageTagMutability",
        "ecr:GetAuthorizationToken",
        "ecr:ListTagsForResource",
        "ecr:UploadLayerPart",
        "ecr:ListImages",
        "ecr:PutImage",
        "ecr:UntagResource",
        "ecr:CompleteLayerUpload",
        "ecr:DescribeImages",
        "ecr:TagResource",
        "ecr:DescribeRepositories",
        "ecr:InitiateLayerUpload",
        "ecr:BatchCheckLayerAvailability"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
EOF
}
terraform apply

Pipeline

我們在 Upload to S3Deploy 中間
加一個建置 Docker 並上傳到 ECR 的步驟

比較特別的是
根據官網描述的使用方式
我們需要先呼叫一個 get-login 的語法取得密碼才能做登入
使用者名稱統一是 AWS
看起來 ECR 底層被 AWS 包的很好
這樣才能統一用 IAM 管理所有權限

    stage('Upload to S3') {
        steps {
            echo 'Upload...'
            dir('ithome-ironman') {
                sh "docker run --rm -v ${WORKSPACE}/ithome-ironman:/app -v /usr/local/src/aws_docker_file/.aws:/root/.aws mikesir87/aws-cli aws s3 cp /app/portal.tar.gz s3://ithome-ironman-markmew-jenkins/portal/stage/portal-${env.BUILD_ID}.tar.gz"
            }
        }
    }
    stage('Build and Push Docker Image') {
        steps {
            script {
                dir('ithome-ironman') {
                    echo 'Building...'
                    def customImage = docker.build("portal:${env:BUILD_ID}", "./")
                    echo 'Validating... aws'
                    sh 'docker run --rm -v /usr/local/src/aws_docker_file/.aws:/root/.aws mikesir87/aws-cli aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 帳號.dkr.ecr.ap-northeast-1.amazonaws.com'
                    echo 'Tag docker image'
                    sh "docker tag portal:${env:BUILD_ID} ECR位置:${env:BUILD_ID}"
                    sh "docker tag portal:${env:BUILD_ID} ECR位置:latest"
                    echo 'Push docker image'
                    sh "docker push ECR位置:${env:BUILD_ID}"
                    sh "docker push ECR位置:latest"
                }
            }
        }
    }
    stage('Deploy') {
        steps {
            echo 'Deploy ...'
            dir('ithome-ironman') {
                sh "docker run --rm -v /usr/local/src/aws_docker_file/.aws:/root/.aws mikesir87/aws-cli aws deploy create-deployment --application-name ithome-ironman-portal --deployment-config-name CodeDeployDefault.OneAtATime --deployment-group-name ithome-ironman-portal --description \"codedeploy test\" --s3-location bucket=ithome-ironman-markmew-jenkins,bundleType=tgz,key=portal/stage/portal-${env.BUILD_ID}.tar.gz"
            }
        }
    }

將 Portal 的程式碼 checkin 進 git

git add Dockerfile 
git add apache2.conf
git commit -m "Build docker container for portal"
git push

然後就可以讓整個流程自動化跑起來

https://ithelp.ithome.com.tw/upload/images/20210929/20141518Cm9j30cN5S.png

流程如果順利執行完
就可以到 ECR 看到剛剛 Push 的 Container 囉

https://ithelp.ithome.com.tw/upload/images/20210929/20141518s8hFz7BuPh.png

雖然我比較想要
帶大家一步一步走
做到打造一個服務並放上
但是每個需要知道的背景知識似乎有點多
沒有解釋的部分
可能也要參考其他前輩寫的教學文了

參考資料:

  1. 將亞馬遜 ECR 與AWS CLI

上一篇
EP16 - 用生活化的例子解釋容器,是否搞錯了些什麼
下一篇
EP18 - 歡迎來到容器管理工具的 EKS
系列文
關於我幫新公司建立整套部屬流程那檔事30

尚未有邦友留言

立即登入留言