昨天我們簡單介紹什麼是容器,
今天我們要開始實戰,
將你的 Django Portal,
逐步包裝成容器,
並放到 AWS 的容器儲存庫(ECR),
中間的過程,
我們會再把之前建置環境的過程簡略帶過,
建置完成後會將建置完成的容器,
在 CI/CD 的過程中,
將容器推送到 AWS 容器儲存庫(ECR)。
(還不會部署到 Kubernetes)
Amazon Elastic Container Registry (Amazon ECR) 是一個全受管容器登錄檔
可隨時隨地輕鬆存放、管理、共享及部署容器映像和成品
Amazon ECR 讓您不必操作自己的容器儲存庫
也不必擔心如何擴展基礎設施
Amazon ECR 會將您的映像託管在高可用性及高效能架構中
讓您可靠地為您的容器應用程式部署映像
您可以在組織內以私有方式共享容器軟體
也可以在世界範圍內以公有方式共享
讓任何人都可以探索和下載
所以使用容器儲存庫
不一定要使用 Dockerhub
使用 Amazon ECR 一樣可公開可私有
不要懷疑
建立一個 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 Cloud Console
查看是否有正常建立
在搜尋框內輸入 ECR
點入後就可以看見容器儲存庫的列表
在 EP10 - Django 持續整合持續部署使用 Jenkins 和 AWS CodeDeploy 的時候
我們有介紹過如何在 Jenkins Server 上安裝 Docker
這次我們要安裝的環境是本機的 vagrant
因為 Ubuntu 版本相同
所以也比照同樣方式安裝就可以了
不過因為我們沒有在本機跑 Jenkins
所以最後 Grant Jenkins 的權限就不用做
只需要 Grant 給 vagrant 就可以了
那我們先到我們虛擬機械的 console
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt update
sudo apt-get install docker-ce
docker --version
sudo systemctl start docker
sudo systemctl enable docker
sudo chown vagrant:docker /var/run/docker.sock
sudo chmod 644 /var/run/docker.sock
上次我們在 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
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 .
因為 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
以最小權限來看
至少我們需要以下權限
因為 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
我們在 Upload to S3
和 Deploy
中間
加一個建置 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"
}
}
}
git add Dockerfile
git add apache2.conf
git commit -m "Build docker container for portal"
git push
然後就可以讓整個流程自動化跑起來
流程如果順利執行完
就可以到 ECR 看到剛剛 Push 的 Container 囉
雖然我比較想要
帶大家一步一步走
做到打造一個服務並放上
但是每個需要知道的背景知識似乎有點多
沒有解釋的部分
可能也要參考其他前輩寫的教學文了
參考資料: