CodeStarでDjangoアプリを作成し、CodePiplineによってCI/CDしていきましょう。
開発環境
ローカルマシン
- git for windows
- anaconda 2019
- python 3.6.9
- Django 1.11.18 / 2.2.9
AWS EC2
- python 3.6.8
- Django 1.11.18 / 2.2.9
プロジェクトの作成
1.aws consoleにログイン、CodeStarを開く(の前にIAMユーザーにAWSCloudFormationFullAccessが必要だったかも)
2.新規プロジェクトの作成をクリック
3.Python(Django)、Amazon EC2を選択
4.プロジェクト名(例:helloworld)を入力し、次へをクリック
5.プロジェクトの詳細を確認したら、プロジェクトを作成するをクリック
6.Amazon EC2 管理コンソールを新しいタブで開き
7.キーペアを作成
9.作成したキーペアを選択し、プロジェクトを作成するをクリック
10.スキップ(オレゴンリージョンにはAWS Cloud9があった)
12.CodePiplineのSource→Build→Deployが完了するまで待つ
※オレゴンリージョンだとエラーが出た
Your requested instance type (t2.micro) is not supported in your requested Availability Zone (us-west-2d). Please retry your request by not specifying an Availability Zone or choosing us-west-2a, us-west-2b, us-west-2c. (Service: AmazonEC2; Status Code: 400; Error Code: Unsupported; Request ID: 7ee63da9-fc85-4e2a-999c-629fe3997dbe)
13.Deployが完了したら、アプリケーションのエンドポイントを開く
※EC2を停止/起動するとページが見えなくなるので、起動時にDjangoアプリも起動するようにスクリプトを設定する必要がある。後述。
プロジェクトの編集(CodeCommitの練習)
1.CodeStarのダッシュボードからコードをクリックするとCodeCommitへ遷移
3.git for Windowsをインストール
4.anaconda promptを開く
5.django1とdjango2の仮想環境を作っておく
conda create -n django1 python=3.6
conda create -n django2 python=3.6
6.IAMのCodeCommitの設定
IAMグループのアクセス許可は下記のような感じ。
AmazonEC2FullAccess
AWSCodeCommitFullAccess
AWSLambdaFullAccess
AmazonS3FullAccess
AmazonDynamoDBFullAccess
AmazonSageMakerFullAccess
AmazonSageMaker-ExecutionPolicy-20191208Txxxxxx
AWSCloudFormationFullAccess
※このユーザー名とパスワードはgit cloneする際に必要なのでメモっておくこと
※パスワードを変更したら、git for Windowsを使用していて認証情報をリセットする方法
7.django1環境をactivateし、プロジェクトをクローン。設定したユーザー名とパスワードを聞かれる。あとはgit configure。
(base) $ conda activate django1
(django1) $ git config --global user.email "s-fujimoto@knowledgecommunication.jp"
(django1) $ git config --global user.name "s-fujimoto"
(django1) $ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/helloworld
(django1) $ cd helloworld
8.developブランチを切る ※masterに直接コミットしない。プロジェクト作成したらすぐにdevelopブランチを切る。
(django1) $ git branch develop
(django1) $ git checkout develop
Switched to branch 'develop'
9.featureブランチを切る ※developに直接コミットしない。新機能の追加や変更を行うたびにfeatureブランチを切る。
(django1) $ git branch feature
(django1) $ git checkout feature
Switched to branch 'feature'
10.プロジェクトに必要なライブラリをインストール
(django1) $ pip install -r requirements/dev.txt
Collecting Django==1.11.18
Using cached https://files.pythonhosted.org/packages/e0/eb/6dc122c6d0a82263bd26bebae3cdbafeb99a7281aa1dae57ca1f645a9872/Django-1.11.18-py2.py3-none-any.whl
Collecting pytz
Using cached https://files.pythonhosted.org/packages/e7/f9/f0b53f88060247251bf481fa6ea62cd0d25bf1b11a87888e53ce5b7c8ad2/pytz-2019.3-py2.py3-none-any.whl
Installing collected packages: pytz, Django
Successfully installed Django-1.11.18 pytz-2019.3
Django 1.11.18がインストールされる
11.Djangoをローカルで起動
(django1) $ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
December 23, 2019 - 18:15:46
Django version 1.11.18, using settings 'ec2django.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[23/Dec/2019 18:15:54] "GET / HTTP/1.1" 200 6652
[23/Dec/2019 18:15:54] "GET /static/helloworld/css/styles.css HTTP/1.1" 200 2690
[23/Dec/2019 18:15:54] "GET /static/helloworld/js/set-background.js HTTP/1.1" 200 137
[23/Dec/2019 18:15:54] "GET /static/helloworld/css/gradients.css HTTP/1.1" 200 2133
[23/Dec/2019 18:15:54] "GET /static/helloworld/img/tweet.svg HTTP/1.1" 200 1418
[23/Dec/2019 18:15:54] "GET /favicon.ico HTTP/1.1" 404 77
12.ブラウザから http://127.0.0.1:8000/ にアクセスして同じページが見えればOK
13.git statusでプログラムの変更を見る
(django1) $ git status
On branch feature
Untracked files:
(use "git add <file>..." to include in what will be committed)
db.sqlite3
ec2django/__pycache__/
helloworld/__pycache__/
helloworld/migrations/__pycache__/
nothing added to commit but untracked files present (use "git add" to track)
14.__pycache__があるとCodePiplineのデプロイに失敗するので不要、下記.gitignoreファイルを追加(db.sqlite3は任意)
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.idea/
db.sqlite3
migrations/
15.featureへコミット
(django1) $ git add .
(django1) $ git commit -m "add .gitignore"
[feature 6d9a594] add .gitignore
1 file changed, 106 insertions(+)
create mode 100644 .gitignore
(django1) $ git push origin feature
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 939 bytes | 939.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/helloworld
* [new branch] feature -> feature
16.CodeCommitを確認すると、featureブランチが作成されている
20.マージしたらfeatureは消す。(けどまた新機能の追加や変更をするときはfeatureブランチを切る。ローカルにはfeatureブランチが残るのでそのまま使う。)
24.masterへマージされるとCodePiplineが起動
25.ページが見えていることを確認
プロジェクトの編集(Django1からDjango2へ更新)
1.django1環境から抜けて、django2環境へactivate
(django1) $ conda deactivate
(base) $ conda activate django2
(django2) $
2.requirements/common.txtを編集
変更前
# dependencies common to all environments
Django==1.11.18
変更後
# dependencies common to all environments
Django==2.2.9
2.ライブラリのインストール
(django2) $ pip install -r requirements/dev.txt
Collecting Django==2.2.9
Using cached https://files.pythonhosted.org/packages/cb/c9/ef1e25bdd092749dae74c95c2707dff892fde36e4053c4a2354b2303be10/Django-2.2.9-py3-none-any.whl
Collecting pytz
Using cached https://files.pythonhosted.org/packages/e7/f9/f0b53f88060247251bf481fa6ea62cd0d25bf1b11a87888e53ce5b7c8ad2/pytz-2019.3-py2.py3-none-any.whl
Collecting sqlparse
Using cached https://files.pythonhosted.org/packages/ef/53/900f7d2a54557c6a37886585a91336520e5539e3ae2423ff1102daf4f3a7/sqlparse-0.3.0-py2.py3-none-any.whl
Installing collected packages: pytz, sqlparse, Django
Successfully installed Django-2.2.9 pytz-2019.3 sqlparse-0.3.0
Django==2.2.9がインストールされる
3.ec2django/setting.pyを編集
変更前
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
変更後
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',
]
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
USE_I18N = True
USE_L10N = True
USE_TZ = True
4.ec2django/urls.pyを編集
変更前
from django.conf import settings
from django.conf.urls.static import static
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('helloworld.urls')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
変更後
from django.conf import settings
from django.conf.urls.static import static
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('helloworld.urls')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
5.helloworld/templates/index.htmlをhelloworld/templates/helloworld/index.htmlに移動
6.helloworld/urls.py
変更前
# helloworld/urls.py
from django.conf.urls import url
from django.conf.urls.static import static
from helloworld import views
urlpatterns = [
url(r'^$', views.HomePageView.as_view()),
]
変更後
# helloworld/urls.py
from django.urls import path
from django.conf.urls import url
from django.conf.urls.static import static
from helloworld import views
urlpatterns = [
path('', views.HomePageView.as_view(), name='index'),
]
7.helloworld/views.py
変更前
# helloworld/views.py
from django.shortcuts import render
from django.views.generic import TemplateView
# Create your views here.
class HomePageView(TemplateView):
def get(self, request, **kwargs):
return render(request, 'index.html', context=None)
変更後
# helloworld/views.py
from django.shortcuts import render
from django.views.generic import TemplateView
from django.views import generic
# Create your views here.
class HomePageView(generic.TemplateView):
template_name = 'helloworld/index.html'
8.Djangoをローカルで起動し、ブラウザから http://127.0.0.1:8000/ にアクセスして同じページが見えればOK
(django2) $ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
December 23, 2019 - 19:27:46
Django version 2.2.9, using settings 'ec2django.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[23/Dec/2019 19:27:49] "GET / HTTP/1.1" 200 6652
[23/Dec/2019 19:27:49] "GET /static/helloworld/js/set-background.js HTTP/1.1" 200 137
[23/Dec/2019 19:27:49] "GET /static/helloworld/css/gradients.css HTTP/1.1" 200 2133
[23/Dec/2019 19:27:49] "GET /static/helloworld/css/styles.css HTTP/1.1" 200 2690
[23/Dec/2019 19:27:49] "GET /static/helloworld/img/tweet.svg HTTP/1.1" 200 1418
9.featureへコミットして、developにマージ、developからmasterにマージする
(django2) $ git status
On branch feature
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: ec2django/settings.py
modified: ec2django/urls.py
deleted: helloworld/templates/index.html
modified: helloworld/urls.py
modified: helloworld/views.py
modified: requirements/common.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
helloworld/templates/helloworld/
no changes added to commit (use "git add" and/or "git commit -a")
(django2) $ git add .
(django2) $ git commit -m "django1.11.18 -> django2.2.9"
[feature 2226d88] django1.11.18 -> django2.2.9
6 files changed, 12 insertions(+), 12 deletions(-)
rename helloworld/templates/{ => helloworld}/index.html (100%)
(django2) $ git push origin feature
Enumerating objects: 21, done.
Counting objects: 100% (21/21), done.
Delta compression using up to 8 threads
Compressing objects: 100% (10/10), done.
Writing objects: 100% (11/11), 1.27 KiB | 648.00 KiB/s, done.
Total 11 (delta 5), reused 0 (delta 0)
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/helloworld
* [new branch] feature -> feature
13.TeraTermを用いて、EC2へSSH接続(PuTTYならPuTTY genを用いてキーペア.pemを.ppkに変換してから接続する)
14.セキュリティ警告は続行をクリック
17.Djangoを起動
[ec2-user@ip-172-31-14-214 ~]$ sudo su
[root@ip-172-31-14-214 ec2-user]# source /home/ec2-user/environment/bin/activate
(environment) [root@ip-172-31-14-214 ec2-user]# /usr/local/bin/supervisord -c /home/ec2-user/supervisord.conf
Error: Another program is already listening on a port that one of our HTTP servers is configured to use. Shut this program down first before starting supervisord.
For help, use /usr/local/bin/supervisord -h
(environment) [root@ip-172-31-14-214 ec2-user]# pkill supervisord
(environment) [root@ip-172-31-14-214 ec2-user]# /usr/local/bin/supervisord -c /home/ec2-user/supervisord.conf
18.ページは見れないままなので、ログを確認する
(environment) [root@ip-172-31-14-214 ec2-user]# cat /var/log/django-application-stderr.log
・・・
File "/home/ec2-user/environment/local/lib/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 63, in check_sqlite_version
raise ImproperlyConfigured('SQLite 3.8.3 or later is required (found %s).' % Database.sqlite_version)
django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).
[2019-12-23 19:54:59 +0900] [25438] [INFO] Worker exiting (pid: 25438)
[2019-12-23 10:54:59 +0000] [25435] [INFO] Shutting down: Master
[2019-12-23 10:54:59 +0000] [25435] [INFO] Reason: Worker failed to boot.
Django2に更新したためSQLite3のバージョンを更新する必要がある。
19.SQLite3の更新
下記の記事を参考
Django2.2で開発サーバー起動時にSQLite3のエラーが出た場合の対応
(environment) [root@ip-172-31-14-214 ec2-user]# wget https://www.sqlite.org/2019/sqlite-autoconf-3280000.tar.gz
(environment) [root@ip-172-31-14-214 ec2-user]# tar xvfz sqlite-autoconf-3280000.tar.gz
(environment) [root@ip-172-31-14-214 ec2-user]# cd sqlite-autoconf-3280000
(environment) [root@ip-172-31-14-214 ec2-user]# ./configure --prefix=/usr/local
(environment) [root@ip-172-31-14-214 ec2-user]# make
(environment) [root@ip-172-31-14-214 ec2-user]# sudo make install
(environment) [root@ip-172-31-14-214 ec2-user]# sudo find /usr/ -name sqlite3
(environment) [root@ip-172-31-14-214 ec2-user]# cd ../
(environment) [root@ip-172-31-14-214 ec2-user]# rm sqlite-autoconf-3280000.tar.gz
(environment) [root@ip-172-31-14-214 ec2-user]# rm -rf ./sqlite-autoconf-3280000
(environment) [root@ip-172-31-14-214 ec2-user]# /usr/local/bin/sqlite3 --version
3.28.0 2019-04-16 19:49:53 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f83156b50
(environment) [root@ip-172-31-14-214 ec2-user]# /usr/bin/sqlite3 --version
3.7.17 2013-05-20 00:56:22 118a3b35693b134d56ebd780123b7fd6f1497668
(environment) [root@ip-172-31-14-214 ec2-user]# sqlite3 --version
3.7.17 2013-05-20 00:56:22 118a3b35693b134d56ebd780123b7fd6f1497668
(environment) [root@ip-172-31-14-214 ec2-user]# sudo mv /usr/bin/sqlite3 /usr/bin/sqlite3_old
(environment) [root@ip-172-31-14-214 ec2-user]# sudo ln -s /usr/local/bin/sqlite3 /usr/bin/sqlite3
(environment) [root@ip-172-31-14-214 ec2-user]# export LD_LIBRARY_PATH="/usr/local/lib"
(environment) [root@ip-172-31-14-214 ec2-user]# python
Python 3.6.8 (default, Oct 14 2019, 21:22:53)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlite3
>>> sqlite3.sqlite_version
'3.28.0'
>>> exit()
(environment) [root@ip-172-31-14-214 ec2-user]#
Djangoを起動しページが見れればOK
(environment) [root@ip-172-31-14-214 ec2-user]# pkill supervisord
(environment) [root@ip-172-31-14-214 ec2-user]# /usr/local/bin/supervisord -c /home/ec2-user/supervisord.conf
EC2再起動時にDjangoを起動するように設定
1.supervisord.confを修正(ローカルも編集してあとでmasterへマージする)
変更前
[program:djangoproject]
command = environment/bin/gunicorn -b 0.0.0.0:80 ec2django.wsgi
変更後
[program:djangoproject]
command = /home/ec2-user/environment/bin/gunicorn -b 0.0.0.0:80 ec2django.wsgi
2.サービスの登録
(environment) [root@ip-172-31-14-214 ec2-user]# vi /etc/init.d/helloworld
#!/bin/sh
# chkconfig: 2345 99 10
# description: start helloworld
# processname: helloworld
start() {
echo "start"
source /home/ec2-user/environment/bin/activate
export LD_LIBRARY_PATH="/usr/local/lib"
/usr/local/bin/supervisord -c /home/ec2-user/supervisord.conf
}
stop() {
echo "stop"
pkill supervisord
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
esac
exit 0
(environment) [root@ip-172-31-14-214 ec2-user]# chkconfig --add helloworld
(environment) [root@ip-172-31-14-214 ec2-user]# chkconfig helloworld on
(environment) [root@ip-172-31-14-214 ec2-user]# chmod u+x /etc/init.d/helloworld
(environment) [root@ip-172-31-14-214 ec2-user]# service helloworld restart
3.再起動してもページが見えればOK(EC2の停止・起動でもDjangoは起動)
(environment) [root@ip-172-31-14-214 ec2-user]# sudo reboot
※EC2を停止・起動するとIP変わるので注意
4.featureのsupervisord.confを修正しコミット、featureからdevelopへマージ、developからmasterへマージする
(django2) $ git status
On branch feature
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: supervisord.conf
no changes added to commit (use "git add" and/or "git commit -a")
(django2) $ git add .
(django2) D:\Users\s-fujimoto\CodeStar\helloworld>git commit -m "modified supervisord.conf"
[feature 38c0e8c] modified supervisord.conf
1 file changed, 1 insertion(+), 1 deletion(-)
(django2) D:\Users\s-fujimoto\CodeStar\helloworld>git push origin feature
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 325 bytes | 325.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/helloworld
* [new branch] feature -> feature
5.CodePiplineが完了し、ページが見えればOK
(6.見えないときはEC2を再起動するか、SSHで接続しログの確認とサービスを再起動する)
(environment) [root@ip-172-31-14-214 ec2-user]# cat /var/log/django-application-stderr.log
(environment) [root@ip-172-31-14-214 ec2-user]# cat /var/log/django-application-stdout.log
(environment) [root@ip-172-31-14-214 ec2-user]# service helloworld restart
強行デプロイ
一度デプロイ失敗してる状態から再試行してもまた失敗する。なので強行デプロイしてから、masterへマージすると成功する。
appspec.ymlとbuildspec.ymlにfileを追記しなかったために,沼にハマった話
1.CodeDeployのアプリケーションから対象を選択
2.失敗したリビジョンの場所を指定
3.デプロイを失敗させないにチェックし、コンテンツの上書きを選択、デプロイの作成からデプロイ
カスタムユーザーモデルを使用する(割愛)
db.sqlite3は.gitignoreされるので、Tera Termで送信
AWS EC2(Linux系)の接続方法とファイル転送方法
DBの更新が必要
python manage.py makemigrations helloworld
python manage.py migrate
プロジェクトの削除
1.CodeStarのプロジェクト一覧から削除
2.プロジェクト名を入力し削除
まとめ
Djangoアプリ開発がかなり楽になった気がする。CICDがんばろー。