├── .bin ├── docker_push.sh ├── ecr_credentials.sh └── ecs_deploy.sh ├── .config ├── app.nginx ├── gunicorn_cfg.py └── supervisord.conf ├── .gitignore ├── .travis.yml ├── Dockerfile ├── Dockerfile.base ├── Pipfile ├── Pipfile.lock ├── README.md ├── app ├── config │ ├── __init__.py │ ├── settings │ │ ├── __init__.py │ │ ├── base.py │ │ ├── dev.py │ │ └── production.py │ ├── urls.py │ └── wsgi │ │ ├── __init__.py │ │ ├── dev.py │ │ └── production.py └── manage.py ├── images ├── deploy_001.jpg ├── deploy_002.jpg ├── deploy_003.jpg ├── deploy_004.jpg ├── deploy_005.jpg ├── deploy_006.jpg ├── deploy_007.jpg ├── deploy_008.jpg ├── deploy_009.jpg ├── deploy_010.jpg ├── deploy_011.jpg ├── deploy_012.jpg ├── deploy_013.jpg ├── deploy_014.jpg ├── deploy_015.jpg ├── deploy_016.jpg ├── deploy_017.jpg ├── deploy_018.jpg ├── deploy_019.jpg ├── deploy_020.jpg ├── deploy_021.jpg ├── deploy_022.jpg ├── deploy_023.jpg ├── deploy_024.jpg ├── deploy_025.jpg ├── deploy_026.jpg ├── deploy_027.jpg ├── deploy_028.jpg ├── deploy_029.jpg ├── deploy_030.jpg ├── deploy_031.jpg ├── deploy_032.jpg ├── deploy_033.jpg ├── deploy_034.jpg ├── deploy_035.jpg ├── deploy_036.jpg ├── deploy_037.jpg ├── deploy_038.jpg ├── deploy_039.jpg ├── deploy_040.jpg ├── deploy_041.jpg ├── deploy_042.jpg ├── deploy_043.jpg ├── deploy_044.jpg ├── deploy_045.jpg ├── deploy_046.jpg ├── deploy_047.jpg ├── deploy_048.jpg ├── deploy_049.jpg ├── deploy_050.jpg └── deploy_051.jpg └── secrets.tar.enc /.bin/docker_push.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Push only if it's not a pull request 3 | if [ -z "$TRAVIS_PULL_REQUEST" ] || [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 4 | # Push only if we're testing the master branch 5 | if [ "$TRAVIS_BRANCH" == "master" ]; then 6 | 7 | # This is needed to login on AWS and push the image on ECR 8 | # Change it accordingly to your docker repo 9 | eval $(aws ecr get-login --no-include-email --region ap-northeast-2) 10 | 11 | # Build and push 12 | docker build -t $IMAGE_NAME:base -f Dockerfile.base . 13 | docker build -t $IMAGE_NAME . 14 | echo "Pushing $IMAGE_NAME:latest" 15 | docker tag $IMAGE_NAME:latest "$REMOTE_IMAGE_URL:latest" 16 | docker tag $IMAGE_NAME:base "$REMOTE_IMAGE_URL:base" 17 | docker push "$REMOTE_IMAGE_URL:base" 18 | docker push "$REMOTE_IMAGE_URL:latest" 19 | echo "Pushed $IMAGE_NAME:latest" 20 | else 21 | echo "Skipping deploy because branch is not 'master'" 22 | fi 23 | else 24 | echo "Skipping deploy because it's a pull request" 25 | fi 26 | -------------------------------------------------------------------------------- /.bin/ecr_credentials.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | mkdir -p ~/.aws 4 | 5 | cat > ~/.aws/credentials << EOL 6 | [default] 7 | aws_access_key_id = $AWS_ACCESS_KEY 8 | aws_secret_access_key = $AWS_SECRET_ACCESS_KEY 9 | EOL 10 | 11 | cat > ~/.aws/config << EOL 12 | [default] 13 | region = ap-northeast-2 14 | output = json 15 | EOL 16 | -------------------------------------------------------------------------------- /.bin/ecs_deploy.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Deploy only if it's not a pull request 3 | if [ -z "$TRAVIS_PULL_REQUEST" ] || [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 4 | # Deploy only if we're testing the master branch 5 | if [ "$TRAVIS_BRANCH" == "master" ]; then 6 | echo "Deploying $TRAVIS_BRANCH on $TASK_DEFINITION" 7 | ecs-deploy -c $CLUSTER_NAME -n $SERVICE_NAME -i $REMOTE_IMAGE_URL:latest 8 | else 9 | echo "Skipping deploy because it's not an allowed branch" 10 | fi 11 | else 12 | echo "Skipping deploy because it's a PR" 13 | fi 14 | -------------------------------------------------------------------------------- /.config/app.nginx: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | charset utf-8; 4 | client_max_body_size 128M; 5 | 6 | location / { 7 | include proxy_params; 8 | proxy_pass http://unix:/tmp/gunicorn.sock; 9 | } 10 | 11 | location /static/ { 12 | alias /srv/projects/.static/; 13 | } 14 | } -------------------------------------------------------------------------------- /.config/gunicorn_cfg.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | 3 | bind = 'unix:/tmp/gunicorn.sock' 4 | workers = multiprocessing.cpu_count() * 2 + 1 5 | errorlog = '/var/log/gunicorn_errors.log' 6 | -------------------------------------------------------------------------------- /.config/supervisord.conf: -------------------------------------------------------------------------------- 1 | [program:gunicorn] 2 | command=gunicorn config.wsgi.production -c ../.config/gunicorn_cfg.py 3 | directory=/srv/projects/app 4 | 5 | [program:nginx] 6 | command = nginx -g 'daemon off;' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Secret Folder 2 | .secrets/ 3 | secrets.tar 4 | 5 | # Created by https://www.gitignore.io/api/git,linux,macos,python,virtualenv,pycharm+all 6 | # Edit at https://www.gitignore.io/?templates=git,linux,macos,python,virtualenv,pycharm+all 7 | 8 | ### Git ### 9 | # Created by git for backups. To disable backups in Git: 10 | # $ git config --global mergetool.keepBackup false 11 | *.orig 12 | 13 | # Created by git when using merge tools for conflicts 14 | *.BACKUP.* 15 | *.BASE.* 16 | *.LOCAL.* 17 | *.REMOTE.* 18 | *_BACKUP_*.txt 19 | *_BASE_*.txt 20 | *_LOCAL_*.txt 21 | *_REMOTE_*.txt 22 | 23 | ### Linux ### 24 | *~ 25 | 26 | # temporary files which can be created if a process still has a handle open of a deleted file 27 | .fuse_hidden* 28 | 29 | # KDE directory preferences 30 | .directory 31 | 32 | # Linux trash folder which might appear on any partition or disk 33 | .Trash-* 34 | 35 | # .nfs files are created when an open file is removed but is still being accessed 36 | .nfs* 37 | 38 | ### macOS ### 39 | # General 40 | .DS_Store 41 | .AppleDouble 42 | .LSOverride 43 | 44 | # Icon must end with two \r 45 | Icon 46 | 47 | # Thumbnails 48 | ._* 49 | 50 | # Files that might appear in the root of a volume 51 | .DocumentRevisions-V100 52 | .fseventsd 53 | .Spotlight-V100 54 | .TemporaryItems 55 | .Trashes 56 | .VolumeIcon.icns 57 | .com.apple.timemachine.donotpresent 58 | 59 | # Directories potentially created on remote AFP share 60 | .AppleDB 61 | .AppleDesktop 62 | Network Trash Folder 63 | Temporary Items 64 | .apdisk 65 | 66 | ### PyCharm+all ### 67 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 68 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 69 | 70 | # User-specific stuff 71 | .idea/**/workspace.xml 72 | .idea/**/tasks.xml 73 | .idea/**/usage.statistics.xml 74 | .idea/**/dictionaries 75 | .idea/**/shelf 76 | 77 | # Generated files 78 | .idea/**/contentModel.xml 79 | 80 | # Sensitive or high-churn files 81 | .idea/**/dataSources/ 82 | .idea/**/dataSources.ids 83 | .idea/**/dataSources.local.xml 84 | .idea/**/sqlDataSources.xml 85 | .idea/**/dynamic.xml 86 | .idea/**/uiDesigner.xml 87 | .idea/**/dbnavigator.xml 88 | 89 | # Gradle 90 | .idea/**/gradle.xml 91 | .idea/**/libraries 92 | 93 | # Gradle and Maven with auto-import 94 | # When using Gradle or Maven with auto-import, you should exclude module files, 95 | # since they will be recreated, and may cause churn. Uncomment if using 96 | # auto-import. 97 | # .idea/modules.xml 98 | # .idea/*.iml 99 | # .idea/modules 100 | 101 | # CMake 102 | cmake-build-*/ 103 | 104 | # Mongo Explorer plugin 105 | .idea/**/mongoSettings.xml 106 | 107 | # File-based project format 108 | *.iws 109 | 110 | # IntelliJ 111 | out/ 112 | 113 | # mpeltonen/sbt-idea plugin 114 | .idea_modules/ 115 | 116 | # JIRA plugin 117 | atlassian-ide-plugin.xml 118 | 119 | # Cursive Clojure plugin 120 | .idea/replstate.xml 121 | 122 | # Crashlytics plugin (for Android Studio and IntelliJ) 123 | com_crashlytics_export_strings.xml 124 | crashlytics.properties 125 | crashlytics-build.properties 126 | fabric.properties 127 | 128 | # Editor-based Rest Client 129 | .idea/httpRequests 130 | 131 | # Android studio 3.1+ serialized cache file 132 | .idea/caches/build_file_checksums.ser 133 | 134 | ### PyCharm+all Patch ### 135 | # Ignores the whole .idea folder and all .iml files 136 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 137 | 138 | .idea/ 139 | 140 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 141 | 142 | *.iml 143 | modules.xml 144 | .idea/misc.xml 145 | *.ipr 146 | 147 | ### Python ### 148 | # Byte-compiled / optimized / DLL files 149 | __pycache__/ 150 | *.py[cod] 151 | *$py.class 152 | 153 | # C extensions 154 | *.so 155 | 156 | # Distribution / packaging 157 | .Python 158 | build/ 159 | develop-eggs/ 160 | dist/ 161 | downloads/ 162 | eggs/ 163 | .eggs/ 164 | lib/ 165 | lib64/ 166 | parts/ 167 | sdist/ 168 | var/ 169 | wheels/ 170 | share/python-wheels/ 171 | *.egg-info/ 172 | .installed.cfg 173 | *.egg 174 | MANIFEST 175 | 176 | # PyInstaller 177 | # Usually these files are written by a python script from a template 178 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 179 | *.manifest 180 | *.spec 181 | 182 | # Installer logs 183 | pip-log.txt 184 | pip-delete-this-directory.txt 185 | 186 | # Unit test / coverage reports 187 | htmlcov/ 188 | .tox/ 189 | .nox/ 190 | .coverage 191 | .coverage.* 192 | .cache 193 | nosetests.xml 194 | coverage.xml 195 | *.cover 196 | .hypothesis/ 197 | .pytest_cache/ 198 | 199 | # Translations 200 | *.mo 201 | *.pot 202 | 203 | # Django stuff: 204 | *.log 205 | local_settings.py 206 | db.sqlite3 207 | 208 | # Flask stuff: 209 | instance/ 210 | .webassets-cache 211 | 212 | # Scrapy stuff: 213 | .scrapy 214 | 215 | # Sphinx documentation 216 | docs/_build/ 217 | 218 | # PyBuilder 219 | target/ 220 | 221 | # Jupyter Notebook 222 | .ipynb_checkpoints 223 | 224 | # IPython 225 | profile_default/ 226 | ipython_config.py 227 | 228 | # pyenv 229 | .python-version 230 | 231 | # celery beat schedule file 232 | celerybeat-schedule 233 | 234 | # SageMath parsed files 235 | *.sage.py 236 | 237 | # Environments 238 | .env 239 | .venv 240 | env/ 241 | venv/ 242 | ENV/ 243 | env.bak/ 244 | venv.bak/ 245 | 246 | # Spyder project settings 247 | .spyderproject 248 | .spyproject 249 | 250 | # Rope project settings 251 | .ropeproject 252 | 253 | # mkdocs documentation 254 | /site 255 | 256 | # mypy 257 | .mypy_cache/ 258 | .dmypy.json 259 | dmypy.json 260 | 261 | # Pyre type checker 262 | .pyre/ 263 | 264 | ### Python Patch ### 265 | .venv/ 266 | 267 | ### VirtualEnv ### 268 | # Virtualenv 269 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 270 | [Bb]in 271 | [Ii]nclude 272 | [Ll]ib 273 | [Ll]ib64 274 | [Ll]ocal 275 | [Ss]cripts 276 | pyvenv.cfg 277 | pip-selfcheck.json 278 | 279 | # End of https://www.gitignore.io/api/git,linux,macos,python,virtualenv,pycharm+all 280 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | language: python 7 | python: 8 | - 3.6.6 9 | 10 | install: 11 | - pip install pipenv 12 | - pipenv install --system --ignore-pipfile 13 | - pipenv install awscli --system --ignore-pipfile 14 | - sudo apt-get install jq 15 | - curl https://raw.githubusercontent.com/silinternational/ecs-deploy/master/ecs-deploy | sudo tee /usr/bin/ecs-deploy 16 | - sudo chmod +x /usr/bin/ecs-deploy 17 | 18 | script: 19 | - python app/manage.py test 20 | 21 | before_install: 22 | - openssl aes-256-cbc -K $encrypted_2ca9d583100c_key -iv $encrypted_2ca9d583100c_iv 23 | -in secrets.tar.enc -out secrets.tar -d 24 | - tar -xvf secrets.tar 25 | 26 | after_success: 27 | - bash .bin/ecr_credentials.sh 28 | - bash .bin/docker_push.sh 29 | - bash .bin/ecs_deploy.sh -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ecs-deploy:base 2 | 3 | COPY ./ /srv/projects 4 | 5 | WORKDIR /srv/projects/app 6 | RUN python3 ./manage.py collectstatic --noinput 7 | 8 | RUN rm -rf /etc/nginx/sites-enabled/* && \ 9 | rm -rf /etc/nginx/sites-available/* && \ 10 | cp -f /srv/projects/.config/app.nginx /etc/nginx/sites-available/ && \ 11 | ln -sf /etc/nginx/sites-available/app.nginx /etc/nginx/sites-enabled/app.nginx 12 | 13 | RUN cp -f /srv/projects/.config/supervisord.conf /etc/supervisor/conf.d/ 14 | CMD supervisord -n -------------------------------------------------------------------------------- /Dockerfile.base: -------------------------------------------------------------------------------- 1 | FROM python:3.6.7-slim 2 | MAINTAINER teachmesomething2580@gmail.com 3 | 4 | ENV DJANGO_SETTINGS_MODULE config.settings.production 5 | ENV LANG C.UTF-8 6 | ENV LC_ALL C.UTF-8 7 | 8 | RUN apt -y update 9 | RUN apt -y dist-upgrade 10 | RUN apt -y install supervisor nginx && \ 11 | pip3 install gunicorn && \ 12 | pip3 install pipenv 13 | 14 | COPY Pipfile /tmp/Pipfile 15 | COPY Pipfile.lock /tmp/Pipfile.lock 16 | WORKDIR /tmp/ 17 | RUN pipenv install --system --ignore-pipfile -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | ipython = "*" 8 | awscli = "*" 9 | 10 | [packages] 11 | django = "*" 12 | requests = "*" 13 | 14 | [requires] 15 | python_version = "3.6" 16 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "862bd915dfea7d34a934f036f4ad4c8bacdd836cd3b274e3c29107647c9c0fe0" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "certifi": { 20 | "hashes": [ 21 | "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", 22 | "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" 23 | ], 24 | "version": "==2019.11.28" 25 | }, 26 | "chardet": { 27 | "hashes": [ 28 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 29 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 30 | ], 31 | "version": "==3.0.4" 32 | }, 33 | "django": { 34 | "hashes": [ 35 | "sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a", 36 | "sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038" 37 | ], 38 | "index": "pypi", 39 | "version": "==2.2.10" 40 | }, 41 | "idna": { 42 | "hashes": [ 43 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 44 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 45 | ], 46 | "version": "==2.8" 47 | }, 48 | "pytz": { 49 | "hashes": [ 50 | "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", 51 | "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" 52 | ], 53 | "version": "==2019.3" 54 | }, 55 | "requests": { 56 | "hashes": [ 57 | "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", 58 | "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" 59 | ], 60 | "index": "pypi", 61 | "version": "==2.21.0" 62 | }, 63 | "sqlparse": { 64 | "hashes": [ 65 | "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", 66 | "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" 67 | ], 68 | "version": "==0.3.0" 69 | }, 70 | "urllib3": { 71 | "hashes": [ 72 | "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", 73 | "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" 74 | ], 75 | "version": "==1.24.3" 76 | } 77 | }, 78 | "develop": { 79 | "awscli": { 80 | "hashes": [ 81 | "sha256:30e23622472c367f0c89736feb14bd3c998ce9a1d1491e5e2913790b77973f8c", 82 | "sha256:aa622637e0f377453d178d1b604beebeda3895660e9ef228d9d36ce6dec89c0a" 83 | ], 84 | "index": "pypi", 85 | "version": "==1.16.88" 86 | }, 87 | "backcall": { 88 | "hashes": [ 89 | "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", 90 | "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" 91 | ], 92 | "version": "==0.1.0" 93 | }, 94 | "botocore": { 95 | "hashes": [ 96 | "sha256:2cdf5519ddab95774248a002c24fbd804b5e5b44ed1c25497d34b34a06e9f84a", 97 | "sha256:bd0b042dded14c7b2978d13dd8088f10f05f9e12382e882e03694e9131185358" 98 | ], 99 | "version": "==1.12.78" 100 | }, 101 | "colorama": { 102 | "hashes": [ 103 | "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", 104 | "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" 105 | ], 106 | "version": "==0.3.9" 107 | }, 108 | "decorator": { 109 | "hashes": [ 110 | "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", 111 | "sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" 112 | ], 113 | "version": "==4.4.1" 114 | }, 115 | "docutils": { 116 | "hashes": [ 117 | "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", 118 | "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" 119 | ], 120 | "version": "==0.16" 121 | }, 122 | "ipython": { 123 | "hashes": [ 124 | "sha256:6a9496209b76463f1dec126ab928919aaf1f55b38beb9219af3fe202f6bbdd12", 125 | "sha256:f69932b1e806b38a7818d9a1e918e5821b685715040b48e59c657b3c7961b742" 126 | ], 127 | "index": "pypi", 128 | "version": "==7.2.0" 129 | }, 130 | "ipython-genutils": { 131 | "hashes": [ 132 | "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", 133 | "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" 134 | ], 135 | "version": "==0.2.0" 136 | }, 137 | "jedi": { 138 | "hashes": [ 139 | "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2", 140 | "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5" 141 | ], 142 | "version": "==0.16.0" 143 | }, 144 | "jmespath": { 145 | "hashes": [ 146 | "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", 147 | "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c" 148 | ], 149 | "version": "==0.9.4" 150 | }, 151 | "parso": { 152 | "hashes": [ 153 | "sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57", 154 | "sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095" 155 | ], 156 | "version": "==0.6.1" 157 | }, 158 | "pexpect": { 159 | "hashes": [ 160 | "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", 161 | "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" 162 | ], 163 | "markers": "sys_platform != 'win32'", 164 | "version": "==4.8.0" 165 | }, 166 | "pickleshare": { 167 | "hashes": [ 168 | "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", 169 | "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" 170 | ], 171 | "version": "==0.7.5" 172 | }, 173 | "prompt-toolkit": { 174 | "hashes": [ 175 | "sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4", 176 | "sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31", 177 | "sha256:f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db" 178 | ], 179 | "version": "==2.0.10" 180 | }, 181 | "ptyprocess": { 182 | "hashes": [ 183 | "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", 184 | "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f" 185 | ], 186 | "version": "==0.6.0" 187 | }, 188 | "pyasn1": { 189 | "hashes": [ 190 | "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", 191 | "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba" 192 | ], 193 | "version": "==0.4.8" 194 | }, 195 | "pygments": { 196 | "hashes": [ 197 | "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", 198 | "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" 199 | ], 200 | "version": "==2.5.2" 201 | }, 202 | "python-dateutil": { 203 | "hashes": [ 204 | "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", 205 | "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" 206 | ], 207 | "markers": "python_version >= '2.7'", 208 | "version": "==2.8.1" 209 | }, 210 | "pyyaml": { 211 | "hashes": [ 212 | "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", 213 | "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", 214 | "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", 215 | "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", 216 | "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", 217 | "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", 218 | "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", 219 | "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", 220 | "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", 221 | "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", 222 | "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" 223 | ], 224 | "version": "==3.13" 225 | }, 226 | "rsa": { 227 | "hashes": [ 228 | "sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5", 229 | "sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd" 230 | ], 231 | "version": "==3.4.2" 232 | }, 233 | "s3transfer": { 234 | "hashes": [ 235 | "sha256:90dc18e028989c609146e241ea153250be451e05ecc0c2832565231dacdf59c1", 236 | "sha256:c7a9ec356982d5e9ab2d4b46391a7d6a950e2b04c472419f5fdec70cc0ada72f" 237 | ], 238 | "version": "==0.1.13" 239 | }, 240 | "six": { 241 | "hashes": [ 242 | "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", 243 | "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" 244 | ], 245 | "version": "==1.14.0" 246 | }, 247 | "traitlets": { 248 | "hashes": [ 249 | "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44", 250 | "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7" 251 | ], 252 | "version": "==4.3.3" 253 | }, 254 | "urllib3": { 255 | "hashes": [ 256 | "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", 257 | "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" 258 | ], 259 | "version": "==1.24.3" 260 | }, 261 | "wcwidth": { 262 | "hashes": [ 263 | "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", 264 | "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" 265 | ], 266 | "version": "==0.1.8" 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECS, ECR(혹은 Docker Hub), Travis CI를 이용하여 CI/CD 배포 환경 만들기 2 | 3 | 4 | 5 | ## 환경 소개 6 | 7 | ECR에 Docker Image를 올린 후, ECS에서 ECR에 올린 이미지를 가지고 서비스를 실행한다. 8 | 9 | Travis CI를 이용하여 `master` 브랜치에 PUSH가 발생하면 자동으로 테스트,배포가 이루어지게한다. 10 | 11 | 또한 서비스의 중단 없이 지속적인 배포를 위해 `BlueGreenDeployment`을 사용한다. 12 | 13 | 시스템 구성은 Nginx + Gunicorn + Django 를 사용한다. 14 | 15 | 16 | ## 기본 환경 세팅 17 | 18 | ```bash 19 | # 작성자의 레포지토리의 before_deploy 브랜치를 클론하여 사용한다. 20 | $ git clone --branch before_deploy https://github.com/teachmesomething2580/ecs-deploy.git <프로젝트 이름> 21 | 22 | $ pipenv --python 3.6.6 23 | $ pipenv install --dev 24 | 25 | $ mkdir .secrets .bin 26 | ``` 27 | 28 | 29 | 30 | ### 프로젝트 설정 31 | 32 | [레포지토리](https://github.com/teachmesomething2580/ecs-deploy)의 `defore_deploy`브랜치를 클론하여 사용한다. 33 | 34 | 모든걸 설명하지는 않고 `startproject` 후 달라진 폴더 및 파일만 설명한다. 35 | 36 | 1. DEV, PRODUCTION 환경을 다르게 두기 위한 `settings.py, wsgi` 세분화 37 | 38 | 2. 중요한 내용을 담은 키나 내용은 프로젝트 소스에 들어가지 않도록 `.secrets` 폴더로 옮김 39 | 40 | ```python 41 | # .serets 폴더와 아래 세 파일을 생성한다. 42 | 43 | # .secrets/dev.json, .secrets/production.json 44 | { 45 | "DATABASES": { 46 | "default": { 47 | "ENGINE": "django.db.backends.sqlite3", 48 | "NAME": "db.sqlite3" 49 | } 50 | } 51 | } 52 | 53 | # 시크릿키는 생성하여 사용하는걸 권장한다. `Django SecretKey Generator`라고 구글에 검색하면 최상단에 나온다. 54 | # .secrets/secret.json 55 | { 56 | "SECRET_KEY": "4e+n)vnn_z)&r9%&4lh#+omtgkq7#v&de3rh)n#ky*p(#gp8mz" 57 | } 58 | ``` 59 | 60 | 3. `nginx, gunicorn, supervisord`설정을 담은 `.config` 폴더 61 | 62 | ### 확인법 63 | `export DJANGO_SETTINGS_MODULE=config.settings.dev` 64 | `python app/manage.py runserver`를 실행했을 때 정상적으로 동작하면 된다. 65 | 66 | 67 | ### 자신의 레포지토리로 연결하기 68 | `git remote -v` 했을 때 ecs-deploy로 origin이 연결되어있다. 이를 자신의 레포지토리로 변경하기 위해 자신의 새 레포지토리를 생성한다. 69 | 70 | 그 후 origin remote 를 삭제하고 자신의 레포지토리로 연결한다. 71 | 72 | `git remote remove origin` 73 | 74 | `git remote add origin <자신의 레포지토리 링크>` 75 | 76 | __현재 브랜치가 `before_deploy`임을 주의하자!__ 77 | 78 | `git checkout -b master`를 통해 브랜치를 바꾸어주고 푸쉬하도록한다. 79 | 80 | 81 | ## Travis CI와 연결 82 | 83 | Travis에 PUSH되었을 때 해야할 일을 정의하기위해 최상위 위치에 `.travis.yml`파일을 작성한다. 84 | 85 | ```yaml 86 | sudo: required 87 | 88 | language: python 89 | python: 90 | - 3.6.6 91 | 92 | install: 93 | - pip install pipenv 94 | - pipenv install --system --ignore-pipfile 95 | script: 96 | -python app/manage.py test 97 | ``` 98 | 99 | 100 | 101 | ### 로그인, Travis ci 활성화 102 | 103 | Github을 통해 로그인하고나서 오른쪽 위 프로필을 클릭하면 다음과 같은 화면이 나온다. 104 | 105 | ![Travis 1](images/deploy_003.jpg) 106 | 107 | 자신의 프로젝트를 검색 후 활성화버튼을 누른다. (무언가 경고창이 뜬다면 하라는대로 허용하면 된다.) 108 | 109 | ![Travis 1](images/deploy_005.jpg) 110 | 111 | 그 후 master 브랜치에 git push 명령을 수행하고나면 아래와같은 화면처럼 자동적으로 travis에서 감지하고 실행하게된다. 112 | 113 | ![Travis 1](images/deploy_006.jpg) 114 | 115 | 물론, 실패한다. 116 | 117 | ![Travis 1](images/deploy_007.jpg) 118 | 119 | Build HIstory - #1 errored 부분을 클릭하면 세부사항을 볼 수 있다. 120 | 121 | 세부사항에 들어가서 아래로 내리면 `Job log`탭이 보이고, `Job log`가 활성화된 상태로 아래로 내리면 오류가 발생한 지점을 찾을 수 있다. 122 | 123 | ![Travis 1](images/deploy_008.jpg) 124 | 125 | 당연히 중요한 정보인 `.secrets/`는 프로젝트 소스에 포함되지 이 에러가 발생하는 것은 당연하다! 126 | 127 | 128 | 129 | ### .secrets 폴더를 어떻게 전송할까? 130 | 131 | 프로젝트가 시작할때는 반드시 `.secrets`안의 내용이 필요하게될것이다. 하지만 github에도 올릴 수 없고.. 방법이 없을까 하다가 `travis encrypt`라는 명령어를 찾게되었다. 132 | 133 | 1. Ruby를 설치하고 `travis`를 설치한다. 134 | 135 | `$ gem install travis --user-install` 136 | 137 | 2. secrets 폴더를 압축한다. 138 | 139 | `$ tar -cvf secrets.tar .secrets` 140 | 141 | 3. travis login 142 | 143 | `$ travis login` 144 | 145 | 3-1. travis 가 찾을 수 없다고 나온다면? 146 | travis를 설치할 때 다음과 같은 문구를 확인했을 것이다. 147 | ```bash 148 | WARNING: You don't have /home/m41d/.gem/ruby/2.5.0/bin in your PATH, 149 | gem executables will not run. 150 | Successfully installed travis-1.8.9 151 | Parsing documentation for travis-1.8.9 152 | Done installing documentation for travis after 1 seconds 153 | 1 gem installed 154 | ``` 155 | PATH 환경변수에 gem으로 설치된 프로그램 폴더가 없기 때문에 명령어를 찾지 못하는 것이다. 156 | FULL PATH 를 사용하여 travis를 실행하도록 하자. 157 | 158 | 4. 암호화 159 | 160 | `$ travis encrypt-file secrets.tar --add` 161 | 162 | 5. gitignore에 `secrets.tar` 파일 추가 163 | 164 | ```gitignore 165 | # Secret Folder 166 | .secrets/ 167 | secrets.tar 168 | 169 | ``` 170 | 171 | 6. `.travis.yml`의 before_install에 `secrets.tar.enc`관련 명령어가 추가된 것을 확인 172 | 173 | ```yaml 174 | before_install: 175 | - openssl aes-256-cbc -K $encrypted_2ca9d583100c_key -iv $encrypted_2ca9d583100c_iv 176 | -in secrets.tar.enc -out secrets.tar -d 177 | ``` 178 | 179 | Travis 홈페이지로 와서 하나의 진행중인 프로젝트를 누르고 오른쪽 상단의 More options - Settings 를 클릭한다. 180 | 181 | ![Travis 1](images/deploy_009.jpg) 182 | 183 | 아래로 내리다보면 `Environment Variables`란에 2개의 Key, Value가 추가된것을 확인할 수 있다. 184 | 185 | ![Travis 1](images/deploy_010.jpg) 186 | 187 | Travis CI에 볼 수 없는 환경변수를 사용하고 PUSH가 일어났을 때 그 환경변수를 가져와 `secrets.tar.enc`를 복호화한다. 188 | 189 | 190 | 191 | DEV 환경에서 실행을 확인하기위해`DJANGO_SETTINGS_MODULE`을 `config.settings.dev`도 추가한다. 192 | 193 | ![Travis 1](images/deploy_011.jpg) 194 | 195 | 196 | 197 | 7. `.travis.yml`에 `secrets.tar` 압축 해제 명령을 실행 198 | 199 | ```yaml 200 | before_install: 201 | - openssl aes-256-cbc -K $encrypted_2ca9d583100c_key -iv $encrypted_2ca9d583100c_iv 202 | -in secrets.tar.enc -out secrets.tar -d 203 | - tar -xvf secrets.tar 204 | ``` 205 | 206 | 8. 완성! 207 | 208 | 아래와 같이 성공해야한다. 209 | 210 | ![Travis 1](images/deploy_012.jpg) 211 | 212 | 213 | 214 | ## ECR과 연동 (선택 1) 215 | 216 | 우선 awscli로 ecr에 이미지를 업로드하기위한 유저를 생성해야한다. 217 | 218 | 219 | 220 | ### IAM 유저 생성 221 | 222 | 1. IAM으로 이동하고 사용자 추가를 누른다. (이름 자유, 프로그래밍 방식 액세스) 223 | 224 | ![Travis 1](images/deploy_013.jpg) 225 | 226 | 2. 권한에서 아래 두가지 권한을 필터 후 체크한다. 227 | 228 | ![Travis 1](images/deploy_015.jpg) 229 | 230 | `AmazoneEC2ContainerRegisterFullAccess` 231 | 232 | `AmazoneEC2ContainerServiceRole` 233 | 234 | 235 | 236 | 다음으로 넘기다보면 확인할 수 있는 창 237 | 238 | ![Travis 1](images/deploy_014.jpg) 239 | 240 | 241 | 242 | 3. 두가지를 선택 후 액세스키가 보이는 창까지 넘어온다. 243 | 244 | ![Travis 1](images/deploy_016.jpg) 245 | 246 | 여기서 잠깐! 창을 끄지않고 놔둔 후 로컬 환경으로 돌아가 aws 설정을 완료한다. 247 | 248 | 249 | 250 | #### awscli 설치 후 셋팅 251 | 252 | ```bash 253 | $ pipenv install --dev awscli 254 | 255 | # ~/.aws/config ~/.aws/credentials 를 대화형 방식으로 설정해주는 명령어를 실행한다. 256 | $ aws configure 257 | $ AWS Access Key ID [None]: 복붙해 넣는다! 258 | $ AWS Secret Access Key [None]: "표시"버튼을 누르고 복붙해 넣는다! 259 | $ Default region name [None]: ap-northeast-2 260 | $ Default output format [None]: json 261 | ``` 262 | 263 | 264 | 265 | ### ECR 생성, 푸시 266 | 267 | ECR로 온 후 레포지토리 생성을 클릭한다. 268 | 269 | ![Travis 1](images/deploy_001.jpg) 270 | 271 | 이름은 자유로히 272 | 273 | ![Travis 1](images/deploy_002.jpg) 274 | 275 | 276 | 277 | 생성 후 생성된 레포지토리를 클릭 후 우측 상단의 푸시 명령을 확인한다. 278 | 279 | ![Travis 1](images/deploy_017.jpg) 280 | 281 | ![Travis 1](images/deploy_018.jpg) 282 | 283 | 284 | 285 | 모든 내용은 __복사하여 붙여넣어__ 사용하자. 바꿀 부분만 직접 바꾸는걸 추천한다! 286 | 287 | 288 | 289 | 1. aws docker hub에 로그인 290 | 291 | ```bash 292 | $ sudo $(aws ecr get-login --no-include-email --region ap-northeast-2) 293 | ``` 294 | 295 | 본인 환경에 따라서 관리자 권한의 필요에 따라 `sudo` 명령을 사용하자. 296 | 297 | 2. 로컬에 Dockerfile을 빌드, ECR로 푸시 298 | 299 | 제공된 환경에서는 `Dockerfile`과 `Dockerfile.base`를 나누어 관리하고 있으므로 `Dockerfile.base`를 우선 빌드, 푸시 후 `Dockerfile`을 빌드, 푸시하여야한다. 300 | 여기서 `Dockerfile`의 `FROM <이미지 이름>:base`을 자신의 ECR의 레포지토리 이름으로 고쳐주어야한다. 301 | 302 | ```bash 303 | $ sudo docker build -t ecs-deploy:base -f Dockerfile.base . 304 | $ sudo docker tag ecs-deploy:base <복사하여 가져온 리포지토리>/<프로젝트 이름>:base 305 | $ sudo docker push <복사하여 가져온 리포지토리>/<프로젝트 이름>:base 306 | 307 | $ sudo docker build -t ecs-deploy . 308 | $ sudo docker tag ecs-deploy:latest <복사하여 가져온 리포지토리>/<프로젝트 이름>:latest 309 | $ sudo docker push <복사하여 가져온 리포지토리>/<프로젝트 이름>:latest 310 | ``` 311 | 312 | 모두 완료되었다면 레포지토리를 클릭했을 때 모든 태그가 존재해야한다. 313 | 314 | ![Travis 1](images/deploy_020.jpg) 315 | 316 | 317 | ## Docker Hub (선택 2) 318 | 319 | 1. Docker Hub의 레포지토리 생성 320 | 321 | 2. docker hub login 322 | 323 | ```bash 324 | $ sudo docker login 325 | ``` 326 | 327 | 3. 로컬 빌드, Push 328 | 329 | ```bash 330 | $ docker build -t <프로젝트 이름>:base -f Dockerfile.base . 331 | $ docker push "<프로젝트 이름>:base" 332 | $ docker build -t <프로젝트 이름> . 333 | $ docker push "<프로젝트 이름>:latest" 334 | ``` 335 | 336 | 337 | ## ECS 생성 338 | 339 | 340 | 341 | ### ECS의 기본 요소 342 | 343 | Elastic Container Service 라는 서비스 이름에서 알 수 있듯이 이 서비스는 컨테이너 가상화를 기반으로 동작한다. 344 | 345 | 346 | 347 | #### 클러스터(Cluster) 348 | 349 | - 논리적인 개념으로 ECS의 기본단위이다. 350 | 351 | - 서비스나 태스크가 실행되는 공간이다. 352 | 353 | 354 | 355 | #### 컨테이너 인스턴스(Container Instance) 356 | 357 | - ECS는 컨테이너를 EC2 인스턴스에 올리도록 설계되어 있다. 358 | - 클러스터에 포함되어야한다. 359 | 360 | 361 | 362 | #### 서비스 (Service) 363 | 364 | - Task를 지속적으로 관리하는 단위이다. 365 | - Task를 Cluster에 몇 개 배포할지 결정하고 실제 Task를 외부에 서비스하기위해 ELB(Load Balancer)에 연동되는 부분을 관리한다. 366 | - 만약 실행중인 Task가 작동이 중지되면 이를 감지해 기존의 것을 삭제하고 새로운 Task를 Cluster에 배포하는 고가용성 정책도 실행한다. 367 | 368 | 369 | 370 | #### 작업 (Task) 371 | 372 | - ECS의 최소 실행단위, 하나 이상의 컨테이너의 묶음이다. 373 | 374 | 375 | 376 | #### 작업 정의 (Task Definition) 377 | 378 | - ECS의 태스크를 실행하기 위한 설정 정보를 저장하고 있는 곳이다. 379 | - 리소스 정보, 컨테이너별로 실행하고자하는 정보(CPU, RAM, 포트 포워딩, 이미지 등등)를 포함한다. 380 | 381 | 382 | 383 | ### 작업 정의 생성 384 | 385 | `Task definition` 혹은 `작업 정의` 메뉴로 이동한다. 386 | 387 | ![Travis 1](images/deploy_021.jpg) 388 | 389 | 1. 새 작업 정의 생성 390 | 2. EC2 선택 391 | 3. 세부 정보 구성 392 | 393 | - 작업 정의 이름 394 | 395 | - 컨테이너 정의 - 컨테이너 추가 396 | 397 | - 컨테이너 이름 398 | - 이미지 (ECR의 latest Image URL을 복사하여 붙여넣는다. 만약 Docker hub를 사용하였으면 `<도커허브 아이디>/<프로젝트 이름>` 를 적는다..) 399 | 400 | ![Travis 1](images/deploy_022.jpg) 401 | 402 | - 메모리 제한 (하드 제한 - 256) 403 | 404 | - 포트 매핑 (호스트 포트 - 0, 컨테이너 포트 - 80, 프로토콜 tcp) 405 | 406 | 호스트 포트가 0을 사용하는 이유는, 지속적인 배포(continuous deployment)가 가능하게 하기 위해 동적으로 호스트 포트를 매핑해야하기 때문이다. 407 | 408 | 409 | 410 | ### 클러스터 생성 411 | 412 | `Cluster` 혹은 `cluster`메뉴로 이동한다. 413 | 414 | 1. 클러스터 생성 415 | 2. EC2 Linux + 네트워킹 416 | 417 | - 클러스터 이름 418 | 419 | - EC2 인스턴스 유형 (프리티어를 사용해야하기 때문에 - t2.micro) 420 | - 키 페어 ( 생성 후 등록하는 것을 추천한다. ) 421 | - VPC (기본 값을 사용, 원하는 경우 바꿔주어도 좋다.) 422 | - CIDR 블록 - 10.0.0.0/16 423 | - 서브넷 1 - 10.0.0.0/24 424 | - 서브넷 2 - 10.0.1.0/24 425 | - 보안 그룹 (새 보안 그룹 생성) 426 | 427 | 428 | 429 | 생성을 누르면 자동으로 설정들이 생성된다. 과정이 모두 완료될 때 까지 기다린다. 430 | 431 | ![Travis 1](images/deploy_023.jpg) 432 | 433 | 434 | 435 | ### 서비스 생성 436 | 437 | 위 화면에서 계속되며 클러스터 보기를 누른 후 서비스 탭의 생성 버튼을 누른다. 438 | 439 | #### 서비스 구성 440 | 441 | - 시작 유형 - EC2 442 | - 서비스 이름 - <자유롭게> 443 | - 서비스 유형 - REPLICA 444 | - 작업 개수 - 1 445 | 446 | 447 | 448 | #### 네트워크 구성 449 | 450 | 다음을 눌러 네트워크 구성으로 온다. 451 | 452 | - Application Load Balancer 453 | 454 | - 서비스의 IAM 역할 - AWSServiceRoleForeECS, 없다면 새 역할 생성 455 | 456 | 457 | 458 | #### 로드 밸런서 구성 459 | 460 | ELB를 선택해야하므로 로드 밸런스를 생성한다. 461 | 462 | EC2 서비스의 왼쪽 메뉴의 로드밸런서로 온다. 463 | 464 | 로드 밸런서 생성을 누른다. 465 | 466 | - 유형 선택 - Application Load Balancer 467 | 468 | ##### 로드 밸런서 구성 469 | 470 | - 이름: <자유> 471 | - __가용 영역__ - VPC를 반드시이전에 생성한 것으로 설정해야한다!!! 가용 영역 두개 모두 클릭 472 | 473 | ![Travis 1](images/deploy_024.jpg) 474 | 475 | ##### 보안 설정 구성 476 | 477 | 넘어간다. 478 | 479 | ##### 보안 그룹 구성 480 | 481 | 기존 보안 그룹중 `EC2ContainerService-ecs-deploy-clust... ` 자동생성된 보안 그룹을 선택한다. 482 | 483 | ![Travis 1](images/deploy_026.jpg) 484 | 485 | ##### 라우팅 구성 486 | 487 | - 이름: <자유> 488 | 489 | ![Travis 1](images/deploy_027.jpg) 490 | 491 | ##### 대상 등록 492 | 493 | 넘어간다. 494 | 495 | 496 | 497 | #### 보안 그룹 설정 498 | 499 | 로드밸런서가 생성되었고, EC2의 SSH 허용과 Dynamic 포트 허용을 위해 `EC2 - 보안그룹`으로 이동한다. 500 | 501 | 그룹 이름이`EC2ContainerService-ecs-deploy-clust... `인 것을 클릭 후 인바운드를 추가한다. 502 | 503 | Dynamic Port는 기본적으로 `32768~65535` 사이로 생성해주므로 해당 포트를 모두 허용해준다. 504 | 505 | ![Travis 1](images/deploy_028.jpg) 506 | 507 | 508 | 509 | #### 다시 네트워크 구성 510 | 511 | ELB 이름을 새로고침하면 위에서 만든 로드밸런서의 이름을 선택할 수 있다. 512 | 513 | - 로드를 밸런싱할 컨테이너 - 대상 그룹 이름에서 위의 `라우팅 구성`에서 생성한 이름을 선택할 수 있다. 514 | - 서비스 검색 (선택사항) - 비활성화 515 | 516 | ![Travis 1](images/deploy_029.jpg) 517 | 518 | 519 | 520 | #### Auto Scaling 521 | 522 | - 서비스 Auto Scaling - 조정 안함 523 | 524 | 525 | 526 | 서비스 생성! 527 | 528 | 이제 모든 작업을 완료했고 작업이 돌아가고있는지 확인하자. 529 | 530 | 클러스터 - 클러스터 이름을 선택 531 | 532 | 서비스란과 작업란을 보면 우리가 등록한 것들이 잘 실행되고 있다. 533 | 534 | ![Travis 1](images/deploy_030.jpg) 535 | 536 | ![Travis 1](images/deploy_031.jpg) 537 | 538 | 539 | 540 | ### Health Check 541 | 542 | 하지만 아직 설정해야할것이 남아있다. 543 | 544 | EC2 - 대상 그룹 - 대상 탭을 눌러본다. 545 | 546 | 547 | 548 | AWS Service는 해당 서비스가 제대로 동작하는지 HealtCheck를 통해 해당 서비스가 살았는지 죽었는지 확인한다. `/`로 요청했을 때 200번의 응답이 와야한다. 549 | 550 | ![Travis 1](images/deploy_032.jpg) 551 | 552 | 하지면 현재는 `/`에는 아무것도 없으므로 400번의 응답을 반환한다. 그러므로 ECS는 오류를 인지하고 작업을 삭제하고 새 작업을 올리게된다. 553 | 554 | ![Travis 1](images/deploy_033.jpg) 555 | 556 | 557 | 558 | 이 문제를 해결하기위해 ALLOWED_HOSTS에 Health Check를 허용하자. 559 | 560 | requests모듈이 필요하기 때문에 설치. 561 | 562 | ```bash 563 | $ pipenv install requests 564 | ``` 565 | 566 | 567 | 568 | Django의 `config.settings.production.py`로 와서 ALLOWED_HOSTS 코드를 덮어쓴다. 569 | 570 | ```python 571 | # 아마존에서 제공해주는 URL에 접속을 허용하는 코드 572 | ALLOWED_HOSTS = [ 573 | '.amazonaws.com', 574 | ] 575 | 576 | # Health Check 도메인을 허용하는 코드 577 | try: 578 | EC2_IP = requests.get('http://169.254.169.254/latest/meta-data/local-ipv4').text 579 | ALLOWED_HOSTS.append(EC2_IP) 580 | except requests.exceptions.RequestException: 581 | pass 582 | ``` 583 | 584 | 585 | 586 | 아직까지는 자동 배포를 설정하지 않았기때문에 Build를 손수 실행한다. 587 | 588 | [ECR 생성, 푸시](#ECR 생성, 푸시)란을 참고하여 base, lastest 재빌드 후 PUSH를 수행한다. 589 | 590 | 그 후 클러스터 - 클러스터 이름 선택 - 작업 탭에서 실행중인 작업을 중지하면 Service에서 이를 눈치채고 다시 Image를 가져와 작업을 실행한다. 591 | 592 | 593 | 594 | 이렇게 수행해도 Unhealthy가 나온다. 595 | 596 | 왜냐하면 Production 환경에서 `/`에 접근하면 404페이지가 나오기 때문이다. 597 | 598 | ![Travis 1](images/deploy_034.jpg) 599 | 600 | 601 | 602 | 정상적인 페이지를 반환하는 URL에 요청해 상태검사를 하기위해 상태검사 경로를 바꾼다. 603 | 604 | EC2 - 대상 그룹 - 대상 그룹 선택 - 상태 검사 - 상태검사 편집 605 | 606 | ![Travis 1](images/deploy_035.jpg) 607 | 608 | `/admin`페이지로의 접근은 가능하기 때문에 임시방편으로 그렇게 설정해놓았다. 성공 코드는 `301` 609 | 610 | ![Travis 1](images/deploy_037.jpg) 611 | 612 | 다시 작업을 중지하고 대상 그룹에서 기다리다가 healthy가 뜨는 것을 확인한다. 613 | 614 | ![Travis 1](images/deploy_038.jpg) 615 | 616 | 617 | 618 | ## 접속해보기 619 | 620 | 로드밸런서를 통해 접속해야만 웹 서비스 포트를 동적으로 연결시켜준다. 621 | 622 | EC2 - 로드밸런서 - 로드밸런서 선택 - 설명란의 DNS이름을 복사 후 URL로 접속해본다. 623 | 624 | ![Travis 1](images/deploy_039.jpg) 625 | 626 | 627 | 628 | Admin 페이지로 도착했을 때 모든게 잘 동작한다! 629 | 630 | ![Travis 1](images/deploy_040.jpg) 631 | 632 | 633 | 634 | ## Travis ci 를 사용하여 ECR, ECS Blue/Green Deploy하기 635 | 636 | before_install 부분 이외에 모두 추가 혹은 수정하도록하자. 637 | ```yaml 638 | sudo: required 639 | 640 | services: 641 | - docker 642 | 643 | language: python 644 | python: 645 | - 3.6.6 646 | 647 | install: 648 | - pip install pipenv 649 | - pipenv install --system --ignore-pipfile 650 | - pipenv install awscli --system --ignore-pipfile 651 | - sudo apt-get install jq 652 | - curl https://raw.githubusercontent.com/silinternational/ecs-deploy/master/ecs-deploy | sudo tee /usr/bin/ecs-deploy 653 | - sudo chmod +x /usr/bin/ecs-deploy 654 | 655 | script: 656 | - python app/manage.py test 657 | 658 | before_install: 659 | - openssl aes-256-cbc -K $encrypted_3a3073cf4f6a_key -iv $encrypted_3a3073cf4f6a_iv 660 | -in secrets.tar.enc -out secrets.tar -d 661 | - tar -xvf secrets.tar 662 | 663 | after_success: 664 | - bash .bin/ecr_credentials.sh 665 | - bash .bin/docker_push.sh 666 | - bash .bin/ecs_deploy.sh 667 | ``` 668 | 669 | 670 | 671 | `.bin/` 하위에 `ecr_credentials.sh`, `docker_push.sh`, `ecs_deploy.sh`를 생성한다. 672 | 673 | 674 | 675 | `ecr_credentials.sh` 676 | 677 | ```shell 678 | #! /bin/bash 679 | 680 | mkdir -p ~/.aws 681 | 682 | cat > ~/.aws/credentials << EOL 683 | [default] 684 | aws_access_key_id = $AWS_ACCESS_KEY 685 | aws_secret_access_key = $AWS_SECRET_ACCESS_KEY 686 | EOL 687 | 688 | cat > ~/.aws/config << EOL 689 | [default] 690 | region = ap-northeast-2 691 | output = json 692 | EOL 693 | ``` 694 | 695 | Travis CI에 등록한 환경변수를 가져와 credentials를 생성하게 한다. 696 | 697 | 698 | 699 | ### ECR을 사용하였을 때 (선택 1) 700 | 701 | `docker_push.sh` 702 | 703 | ```shell 704 | #! /bin/bash 705 | # GitHub에서 발생한 WebHook이 PUSH일 경우만 실행하도록한다. 706 | if [ -z "$TRAVIS_PULL_REQUEST" ] || [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 707 | # master 브랜치일경우만 push가 실행되도록한다. 708 | if [ "$TRAVIS_BRANCH" == "master" ]; then 709 | 710 | eval $(aws ecr get-login --no-include-email --region ap-northeast-2) 711 | 712 | # Build and push 713 | docker build -t $IMAGE_NAME:base -f Dockerfile.base . 714 | docker build -t $IMAGE_NAME . 715 | echo "Pushing $IMAGE_NAME" 716 | docker tag $IMAGE_NAME:latest "$REMOTE_IMAGE_URL:latest" 717 | docker tag $IMAGE_NAME:base "$REMOTE_IMAGE_URL:base" 718 | docker push "$REMOTE_IMAGE_URL:base" 719 | docker push "$REMOTE_IMAGE_URL:latest" 720 | echo "Pushed $IMAGE_NAME:latest" 721 | 722 | else 723 | echo "Skipping deploy because branch is not 'master'" 724 | fi 725 | else 726 | echo "Skipping deploy because it's a pull request" 727 | fi 728 | 729 | ``` 730 | 731 | ### DockerHub를 사용했을 때 (선택 2) 732 | 733 | `docker_push.sh` 734 | 735 | ```shell 736 | #! /bin/bash 737 | # GitHub에서 발생한 WebHook이 PUSH일 경우만 실행하도록한다. 738 | if [ -z "$TRAVIS_PULL_REQUEST" ] || [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 739 | # master 브랜치일경우만 push가 실행되도록한다. 740 | if [ "$TRAVIS_BRANCH" == "master" ]; then 741 | 742 | docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD 743 | 744 | # Build and push 745 | docker build -t $IMAGE_NAME:base -f Dockerfile.base . 746 | docker push "$IMAGE_NAME:base" 747 | docker build -t $IMAGE_NAME . 748 | docker push "$IMAGE_NAME:latest" 749 | 750 | else 751 | echo "Skipping deploy because branch is not 'master'" 752 | fi 753 | else 754 | echo "Skipping deploy because it's a pull request" 755 | fi 756 | ``` 757 | 758 | 759 | `TRAVIS_`로 시작하는 환경변수는 자동으로 만들어지는 변수로 사용된다. 그러므로 직접 정의해줄 필요는 없다. 760 | 761 | 위에서 작성한 파일들에서 필요한 환경변수들을 정리해보자. 762 | 763 | ```text 764 | AWS_ACCESS_KEY : 말 그대로 765 | AWS_SECRET_ACCESS_KEY : 말 그대로 766 | IMAGE_NAME : 리포지토리 이름 767 | 768 | # ECR이 사용될 때 (선택 1) 769 | REMOTE_IMAGE_URL : 뒤의 tag(latest, base)를 뺀 URL 770 | 771 | # DockerHub가 사용될 때 (선택 2) 772 | DOCKER_USERNAME 773 | DOCKER_PASSWORD 774 | ``` 775 | 776 | 이들을 Travis CI의 Environment Variables에 추가하도록하자. 777 | 778 | 주의할점은 보여지면 안될 비밀 요소들은 모두 `Display value in bulid log`를 끄도록하자. (AWS_ACCESS_KEY, AWS_SECRET_ACCESS_KEY) 이는 빌드 로그에서도 보여지지 않는다. 779 | 780 | 아래와 같이 설정되었는지 확인하자. 781 | 782 | ![Travis 1](images/deploy_041.jpg) 783 | 784 | 785 | 786 | 이후 다시 git push를 했을 때 ECR에 새 Image가 들어온지 확인한다. 같은 태그가 있을경우 최신 것이 태그가 반영되고 기존의 것은 \로 바뀌게된다. 787 | 788 | ![Travis 1](images/deploy_042.jpg) 789 | 790 | 791 | 792 | 이곳까지 완료했으면 이제 blue/green 배포를 위해 ecs-deploy.sh를 작성한다. 793 | 794 | `ecs-deploy.sh` 795 | 796 | ```shell 797 | #! /bin/bash 798 | 799 | if [ -z "$TRAVIS_PULL_REQUEST" ] || [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 800 | if [ "$TRAVIS_BRANCH" == "master" ]; then 801 | 802 | echo "Deploying $TRAVIS_BRANCH on $CLUSTER_NAME" 803 | # ECR 사용했을 시 (선택 1) 804 | ecs-deploy -c $CLUSTER_NAME -n $SERVICE_NAME -i $REMOTE_IMAGE_URL:latest 805 | # Docker Hub 사용했을 시 (선택 2) 806 | ecs-deploy -c $CLUSTER_NAME -n $SERVICE_NAME -i $IMAGE_NAME:latest 807 | 808 | else 809 | echo "Skipping deploy because it's not an allowed branch" 810 | fi 811 | else 812 | echo "Skipping deploy because it's a PR" 813 | fi 814 | 815 | ``` 816 | 817 | 다음의 환경변수들을 추가한다. 818 | 819 | ```text 820 | CLUSTER_NAME 821 | SERVICE_NAME 822 | ``` 823 | 824 | 또한 ecs-deploy를 사용하기위해 IAM의 인라인정책이 필요하다. 825 | 826 | 827 | 828 | 사용중인 IAM을 선택 후 인라인 정책을 선택 829 | 830 | ![Travis 1](images/deploy_043.jpg) 831 | 832 | JSON을 클릭 833 | 834 | ![Travis 1](images/deploy_044.jpg) 835 | 836 | 아래 내용을 붙여넣고 정책 검토 837 | 838 | ``` 839 | { 840 | "Version": "2012-10-17", 841 | "Statement": [ 842 | { 843 | "Effect": "Allow", 844 | "Action": [ 845 | "ecs:DeregisterTaskDefinition", 846 | "ecs:DescribeServices", 847 | "ecs:DescribeTaskDefinition", 848 | "ecs:DescribeTasks", 849 | "ecs:ListTasks", 850 | "ecs:ListTaskDefinitions", 851 | "ecs:RegisterTaskDefinition", 852 | "ecs:StartTask", 853 | "ecs:StopTask", 854 | "ecs:UpdateService", 855 | "iam:PassRole" 856 | ], 857 | "Resource": "*" 858 | } 859 | ] 860 | } 861 | ``` 862 | 863 | ![Travis 1](images/deploy_045.jpg) 864 | 865 | 이름을 생성하고 정책 생성 866 | 867 | ![Travis 1](images/deploy_046.jpg) 868 | 869 | 870 | 871 | 872 | 873 | 그 후 git push를 실행하면 마지막에 다음과 같이 나오고, 서버가 꺼지는일 없이 새로운 환경이 배포된다. 874 | 875 | ![Travis 1](images/deploy_047.jpg) 876 | 877 | 878 | 879 | ## Blue Green Deployment 과정을 더 자세하게 (추측) 880 | 881 | 위의 내용을 Task를 이해하지 못했다면 도커 서비스를 하나 올렸다고 생각하고 현재 동작 방식을 정리한다. 882 | 883 | - Task는 Dynamic하게 중복되지 않는 포트로 생성되어 서비스되어진다. 884 | 885 | - LoadBalancer는 80번포트로 들어온 서비스를 대상 그룹(Target Group) 내 Dynamic하게 생성된 포트의 Task로 연결된다. 886 | - 이 내용을 기반으로 만약 대상 그룹 내 Task가 여러개일경우, 설정에 따라 분산처리된다. (해당 예제의 경우 Cluster 생성시 분산처리를 선택했으므로 각각 한번씩 돌아가며 요청을 받게된다.) 887 | 888 | 889 | 890 | 1. Task Revision을 생성한다. 891 | 892 | ![Travis 1](images/deploy_049.jpg) 893 | 894 | Revision은 특정 버전에 대한 갱신 작업 또는 결과물을 뜻한다. [여기서 참고함](http://ecampus.keris.or.kr/cyber/9/CD2/lecture_23/2301_p1.htm) 맞는것 같기도 하고 아닌것 같기도 하고..? 895 | 896 | 2. 해당 Task를 기존에 동작하던 Cluster에서 실행시킨다. 897 | 898 | ![Travis 1](images/deploy_050.jpg) 899 | 900 | ![Travis 1](images/deploy_051.jpg) 901 | 902 | 여기서 Service를 생성할 때 Task Placement 선택에 따라 동작이 다른데, 기본적으로 `AZ Balanced Spread`이다. 즉, 여러개의 Task가 있다면 요청에 대하여 번갈아가며 처리하는 방식이다. 903 | 904 | 왜 여기서 이걸 설명했냐면, 새 서비스가 올라가는 중 자신의 페이지를 들어가면 (1)변경이 반영안된 서비스, (2) 변경이 반영된 서비스 두 페이지가 새로고침마다 번갈아가며 나타나게된다. 905 | 906 | 3. 새 Task가 완전히 올라왔다면 기존 Task를 삭제한다. 907 | 908 | 909 | 910 | ## 기타 911 | 912 | 이대로 사용할거면 Branch 전략은 `Github flow`가 좋다고 생각한다. 913 | 914 | ## 참고 사이트 915 | 916 | ### ECS, ECR 사용 917 | [https://blog.appkr.kr/work-n-play/deploy-with-ecs/] 918 | ECS TASK 생성 919 | ECS CLUSTER 생성 920 | 921 | ec2-user@> 922 | KEY 오류는 `ec2-user@`를 입력하고 들어가야 됨 923 | cluster, loadbalancer VPC 그룹은 반드시 같게!! 924 | 925 | ### ECS, ECR, Jenkins 모두 이용 (컨테이너 2개이용하는 방법) 926 | https://docs.aws.amazon.com/ko_kr/AWSGettingStartedContinuousDeliveryPipeline/latest/GettingStarted/CICD_Jenkins_Pipeline.html 927 | 928 | ### health check 929 | ``` 930 | ALLOWED_HOSTS = [ 931 | '.amazonaws.com', 932 | ] 933 | 934 | try: 935 | EC2_IP = requests.get('http://169.254.169.254/latest/meta-data/local-ipv4').text 936 | ALLOWED_HOSTS.append(EC2_IP) 937 | except requests.exceptions.RequestException: 938 | pass 939 | ``` 940 | 941 | ### 여러 Task를 올리기위한 Dynamic Port mapping 942 | 943 | #### 작업 정의시 0-80을 사용하라 944 | https://aws.amazon.com/ko/premiumsupport/knowledge-center/dynamic-port-mapping-ecs/ 945 | 946 | #### EC2의 32768-65535 포트를 허용하라. 947 | https://stackoverflow.com/questions/39832433/dynamic-ports-and-aws-applocation-load-balancer-and-ecs 948 | 949 | ### travis ci 950 | https://github.com/silinternational/ecs-deploy 951 | -------------------------------------------------------------------------------- /app/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/app/config/__init__.py -------------------------------------------------------------------------------- /app/config/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/app/config/settings/__init__.py -------------------------------------------------------------------------------- /app/config/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for config project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.1/ref/settings/ 11 | """ 12 | import json 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 17 | ROOT_DIR = os.path.dirname(BASE_DIR) 18 | STATIC_DIR = os.path.join(BASE_DIR, 'static') 19 | SECRET_DIR = os.path.join(ROOT_DIR, '.secrets') 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ 23 | 24 | SECRET_JSON = json.load(open(os.path.join(SECRET_DIR, 'secret.json'))) 25 | 26 | # SECURITY WARNING: keep the secret key used in production secret! 27 | # SECRET_KEY = SECRET_JSON['SECRET_KEY'] 28 | SECRET_KEY = "4e+n)vnn_z)&r9%&4lh#+omtgkq7#v&de3rh)n#ky*p(#gp8mz" 29 | 30 | # Static files (CSS, JavaScript, Images) 31 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 32 | 33 | STATIC_URL = '/static/' 34 | 35 | # Application definition 36 | 37 | INSTALLED_APPS = [ 38 | 'django.contrib.admin', 39 | 'django.contrib.auth', 40 | 'django.contrib.contenttypes', 41 | 'django.contrib.sessions', 42 | 'django.contrib.messages', 43 | 'django.contrib.staticfiles', 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | 'django.middleware.security.SecurityMiddleware', 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'django.middleware.common.CommonMiddleware', 50 | 'django.middleware.csrf.CsrfViewMiddleware', 51 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 52 | 'django.contrib.messages.middleware.MessageMiddleware', 53 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 54 | ] 55 | 56 | ROOT_URLCONF = 'config.urls' 57 | 58 | TEMPLATES = [ 59 | { 60 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 61 | 'DIRS': [], 62 | 'APP_DIRS': True, 63 | 'OPTIONS': { 64 | 'context_processors': [ 65 | 'django.template.context_processors.debug', 66 | 'django.template.context_processors.request', 67 | 'django.contrib.auth.context_processors.auth', 68 | 'django.contrib.messages.context_processors.messages', 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | # Password validation 75 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 76 | 77 | AUTH_PASSWORD_VALIDATORS = [ 78 | { 79 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 80 | }, 81 | { 82 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 83 | }, 84 | { 85 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 86 | }, 87 | { 88 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 89 | }, 90 | ] 91 | 92 | 93 | # Internationalization 94 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 95 | 96 | LANGUAGE_CODE = 'ko-kr' 97 | TIME_ZONE = 'Asia/Seoul' 98 | USE_I18N = True 99 | USE_L10N = True 100 | USE_TZ = True 101 | 102 | -------------------------------------------------------------------------------- /app/config/settings/dev.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | DEV_JSON = json.load(open(os.path.join(SECRET_DIR, 'dev.json'))) 4 | 5 | # SECURITY WARNING: don't run with debug turned on in production! 6 | DEBUG = True 7 | 8 | ALLOWED_HOSTS = [ 9 | '127.0.0.1', 10 | 'localhost', 11 | ] 12 | 13 | STATICFILES_DIRS = [ 14 | STATIC_DIR, 15 | ] 16 | 17 | WSGI_APPLICATION = 'config.wsgi.dev.application' 18 | 19 | 20 | # Database 21 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 22 | 23 | DATABASES = DEV_JSON['DATABASES'] 24 | -------------------------------------------------------------------------------- /app/config/settings/production.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from .base import * 4 | 5 | PRODUCTION_JSON = json.load(open(os.path.join(SECRET_DIR, 'production.json'))) 6 | 7 | # SECURITY WARNING: don't run with debug turned on in production! 8 | DEBUG = False 9 | 10 | # 아마존에서 제공해주는 URL에 접속을 허용하는 코드 11 | ALLOWED_HOSTS = [ 12 | '.amazonaws.com', 13 | ] 14 | 15 | # Health Check 도메인을 허용하는 코드 16 | try: 17 | EC2_IP = requests.get('http://169.254.169.254/latest/meta-data/local-ipv4').text 18 | ALLOWED_HOSTS.append(EC2_IP) 19 | except requests.exceptions.RequestException: 20 | pass 21 | 22 | STATIC_ROOT = os.path.join(ROOT_DIR, '.static') 23 | 24 | WSGI_APPLICATION = 'config.wsgi.production.application' 25 | 26 | 27 | # Database 28 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 29 | 30 | DATABASES = PRODUCTION_JSON['DATABASES'] -------------------------------------------------------------------------------- /app/config/urls.py: -------------------------------------------------------------------------------- 1 | """config URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /app/config/wsgi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/app/config/wsgi/__init__.py -------------------------------------------------------------------------------- /app/config/wsgi/dev.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /app/config/wsgi/production.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /app/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 7 | print('now env: ', os.getenv('DJANGO_SETTINGS_MODULE')) 8 | try: 9 | from django.core.management import execute_from_command_line 10 | except ImportError as exc: 11 | raise ImportError( 12 | "Couldn't import Django. Are you sure it's installed and " 13 | "available on your PYTHONPATH environment variable? Did you " 14 | "forget to activate a virtual environment?" 15 | ) from exc 16 | execute_from_command_line(sys.argv) 17 | -------------------------------------------------------------------------------- /images/deploy_001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_001.jpg -------------------------------------------------------------------------------- /images/deploy_002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_002.jpg -------------------------------------------------------------------------------- /images/deploy_003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_003.jpg -------------------------------------------------------------------------------- /images/deploy_004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_004.jpg -------------------------------------------------------------------------------- /images/deploy_005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_005.jpg -------------------------------------------------------------------------------- /images/deploy_006.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_006.jpg -------------------------------------------------------------------------------- /images/deploy_007.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_007.jpg -------------------------------------------------------------------------------- /images/deploy_008.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_008.jpg -------------------------------------------------------------------------------- /images/deploy_009.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_009.jpg -------------------------------------------------------------------------------- /images/deploy_010.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_010.jpg -------------------------------------------------------------------------------- /images/deploy_011.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_011.jpg -------------------------------------------------------------------------------- /images/deploy_012.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_012.jpg -------------------------------------------------------------------------------- /images/deploy_013.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_013.jpg -------------------------------------------------------------------------------- /images/deploy_014.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_014.jpg -------------------------------------------------------------------------------- /images/deploy_015.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_015.jpg -------------------------------------------------------------------------------- /images/deploy_016.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_016.jpg -------------------------------------------------------------------------------- /images/deploy_017.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_017.jpg -------------------------------------------------------------------------------- /images/deploy_018.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_018.jpg -------------------------------------------------------------------------------- /images/deploy_019.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_019.jpg -------------------------------------------------------------------------------- /images/deploy_020.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_020.jpg -------------------------------------------------------------------------------- /images/deploy_021.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_021.jpg -------------------------------------------------------------------------------- /images/deploy_022.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_022.jpg -------------------------------------------------------------------------------- /images/deploy_023.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_023.jpg -------------------------------------------------------------------------------- /images/deploy_024.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_024.jpg -------------------------------------------------------------------------------- /images/deploy_025.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_025.jpg -------------------------------------------------------------------------------- /images/deploy_026.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_026.jpg -------------------------------------------------------------------------------- /images/deploy_027.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_027.jpg -------------------------------------------------------------------------------- /images/deploy_028.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_028.jpg -------------------------------------------------------------------------------- /images/deploy_029.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_029.jpg -------------------------------------------------------------------------------- /images/deploy_030.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_030.jpg -------------------------------------------------------------------------------- /images/deploy_031.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_031.jpg -------------------------------------------------------------------------------- /images/deploy_032.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_032.jpg -------------------------------------------------------------------------------- /images/deploy_033.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_033.jpg -------------------------------------------------------------------------------- /images/deploy_034.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_034.jpg -------------------------------------------------------------------------------- /images/deploy_035.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_035.jpg -------------------------------------------------------------------------------- /images/deploy_036.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_036.jpg -------------------------------------------------------------------------------- /images/deploy_037.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_037.jpg -------------------------------------------------------------------------------- /images/deploy_038.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_038.jpg -------------------------------------------------------------------------------- /images/deploy_039.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_039.jpg -------------------------------------------------------------------------------- /images/deploy_040.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_040.jpg -------------------------------------------------------------------------------- /images/deploy_041.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_041.jpg -------------------------------------------------------------------------------- /images/deploy_042.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_042.jpg -------------------------------------------------------------------------------- /images/deploy_043.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_043.jpg -------------------------------------------------------------------------------- /images/deploy_044.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_044.jpg -------------------------------------------------------------------------------- /images/deploy_045.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_045.jpg -------------------------------------------------------------------------------- /images/deploy_046.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_046.jpg -------------------------------------------------------------------------------- /images/deploy_047.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_047.jpg -------------------------------------------------------------------------------- /images/deploy_048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_048.jpg -------------------------------------------------------------------------------- /images/deploy_049.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_049.jpg -------------------------------------------------------------------------------- /images/deploy_050.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_050.jpg -------------------------------------------------------------------------------- /images/deploy_051.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/images/deploy_051.jpg -------------------------------------------------------------------------------- /secrets.tar.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsheKR/ecs-deploy/9c18ddf7eda84afcadf00290430c10494fc5c87f/secrets.tar.enc --------------------------------------------------------------------------------