프로덕트

아직 프로덕트가 없습니다.

아티클

전체 보기
Jihwan

Jihwan

CI/CD에 대해 공부해보자 - 3. Docker&AWS

저번 포스트에서 test를 어떻게 하는지 알아봤으니 이제 배포를 해보겠다. 배포에 필요한 서버는 AWS의 ec2인스턴스를 사용하였다.

처음에 말한 github action을 이용해서 CD를 구현할건데 기본적인 flow는 이렇다 (출처 까먹었다)

deployment_flowchart.png

github action (.yml에 정의됨)=> 소스코드 image를 build -> dockerhub에 push -> ec2서버에 접속해서 image pull -> pull 한 image로 container run

이런 식으로 굴러가게 된다

다음은 작성한 .yml, dockerfile, nginx.conf파일이다. 아 nginx.conf은 Django의 static이랑 media 파일들을 특정 포트에 serve해주기 위해서 사용했다. 이걸 안하면 templates에 static 파일 안에 js파일들이 적용이 안 된다..

.yml

name: Django Tests

on:
  push:
    branches:
      - main
      - test
      - depy
  pull_request:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latesta

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'

    - name: Install dependencies
      run: |
        python -m venv venv
        source venv/bin/activate
        pip install django

    - name: Change directory
      run: |
        source venv/bin/activate
        python -m pip install Pillow
        cd SOS
        python manage.py makemigrations question
        python manage.py migrate
        python manage.py test

  deploy:
      runs-on: ubuntu-latest
      needs: test
  
      steps:
      - name: Checkout code
        uses: actions/checkout@v4
  
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
  
      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
  
      - name: Build and push Docker image
        run: |
          cd SOS
          docker build -t sos_init .
          docker tag sos_init ${{ secrets.DOCKER_USERNAME }}/sos:latest
          docker push ${{ secrets.DOCKER_USERNAME }}/sos:latest
  
      - name: Set up SSH
        uses: webfactory/ssh-agent@v0.5.3
        with:
          ssh-private-key: ${{ secrets.EC2_PRIVATE_KEY }}
  
      - name: Deploy to EC2
        run: |
          ssh -o StrictHostKeyChecking=no ec2-user@***.ap-northeast-2.compute.amazonaws.com << 'EOF'
            docker pull ${{ secrets.DOCKER_USERNAME }}/sos:latest
            docker stop django_sos || true
            docker rm django_sos || true
            docker run -d --name django_sos -p 80:80 -e DJANGO_SETTINGS_MODULE=SOS.settings ***/sos:latest
          EOF

밑에 docker run~부분을 보면 -p 80:80이 있는데 저게 호스트의 80포트를 컨테이너의 80포트와 맵핑시키는 역할이다.

http -> 80포트

https -> 443포트

Dockerfile(requirements.txt안에 필요한 dependencies들이 들어있다)

FROM python:3.11.9
WORKDIR /SOS

COPY . /SOS/.

#Dependencies setting
RUN pip install --no-cache-dir -r requirements.txt
RUN apt-get update && apt-get install -y nginx

ENV DJANGO_SETTINGS_MODULE=SOS.settings
ENV PYTHONUNBUFFERED 1

COPY nginx.conf /etc/nginx/sites-available/default
RUN rm -f /etc/nginx/sites-enabled/default && ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled
RUN python manage.py collectstatic --noinput

CMD service nginx start && \ 
    python manage.py makemigrations question && \
    python manage.py migrate && \
    python manage.py load_questions && \ 
    gunicorn --bind 0.0.0.0:8000 SOS.wsgi:application

EXPOSE 80

필요한 dependency들을 깔아주고, 저 collectstatic은 내가 setting.py에 정의한 경로대로 (지금은 static파일) 정적파일을 자동으로 수집해준다.

밑에 CMD부분은 컨테이너가 실행될 때 자동으로 실행해주는 CLI명령어다. database build와 맨 밑에 gunicorn이 있는데 production할 때는 runserver가 아닌 저 명령어로 실행하는 것이 좋다고 한다 (애초에 runserver은 개발 단계에서 사용하기 위해 만들어졌다고 한다).

가장 밑에는 EXPOSE 80이 있는데 저것이 container의 80포트를 외부에 노출시킨다는 것이다.

nginx.conf

server {
    listen 80;
    server_name localhost;

    location /static/ {
        alias /SOS/static/;
    }

    location /media/ {
        alias /SOS/media/;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

nginx가 뭔지 하나도 몰라서 조금 고생했는데(아직도 모름..) 80포트로 들어온 요청 중 url 경로에 media와 static들을 포함한 요청은 파일dir을 맵핑시켜서 처리하고 나머지 요청은 8000포트에서 실행 중인 server에 전달한다는 거 같다.

사실 아직 정리되지 않은 부분이 많은데(event trigger, 도메인 연결..) 마지막 최종 발표 전에 시간이 있으면 정리해야겠다.

정말 배포에 대한 기본지식이 너무 없어서 시간을 꽤나 많이 잡아먹었다. aws는 정말 한번 제대로 공부해봐야겠다. CI/CD 끝!

2
0
Jihwan

Jihwan

CI/CD에 대해 공부해보자 - 2. Testing

저번 포스트에서 github action이 뭔지 다루었으니까, 이번에는 직접 사용을 해보려고 노력을 해보도록 하겠다.

학교 os수업 시간에 pintOS라는 어마무시무시하고, 사람을 무기력하게 만들고, 정신이 나가게 하는 과제를 하고 있는데, 그러면 이것도 action을 사용해서 make check를 돌려서 test를 해 볼 수 있지 않을까? pintOS에 대한 포스트는 project3부터 올려야겠다 (이미 해버려서..)

교수님이 주신 project 2 답지 파일로 해보겠다

일단 해보자!

사용할 수 있는 os version은 여기에 있다(출처: github 공식 홈페이지)

image.png

우리 과제는 ubuntu 14.04를 쓰고 있지만.. 일단 20.04로 해보겠다.

image.png뭔가 이상한데.. 뭔가 되긴 된다. 우분투 버전이 안 맞아서 그런 거 같다.

image.png

추가로 cs50이라는 하버드 강의에도 CI/CD에 대한 내용이 있었다(cs50). 이제 Django test.py를 작성해보자!

from django.test import TestCase, Client
from django.contrib.auth.models import User
from django.urls import reverse

class AuthTests(TestCase):
    def setUp(self):
        self.client = Client()
        self.username = 'testuser'
        self.password = 'testpassword123'
        self.user = User.objects.create_user(username=self.username, password=self.password)

    def test_login_view(self):
        response = self.client.get(reverse('question:index'))
        self.assertEqual(response.status_code, 200)
        
        login_data = {
            'username': self.username,
            'password': self.password,
        }
        response = self.client.post(reverse('question:index'), data=login_data)
        self.assertEqual(response.status_code, 302)  # Redirects to 'question:main'
        self.assertRedirects(response, reverse('question:main'))

        # Check if the user is authenticated
        user = response.wsgi_request.user
        self.assertTrue(user.is_authenticated)
        
    def test_login_invalid_credentials(self):
        login_data = {
            'username': self.username,
            'password': 'wrongpassword',
        }
        response = self.client.post(reverse('question:index'), data=login_data)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Invalid credentials')
        
        # Check if the user is not authenticated
        user = response.wsgi_request.user
        self.assertFalse(user.is_authenticated)

    def test_logout_view(self):
        self.client.login(username=self.username, password=self.password)
        response = self.client.get(reverse('question:logout'))
        self.assertEqual(response.status_code, 302)  # Redirects to 'question:index'
        self.assertRedirects(response, reverse('question:index'))
        
        # Check if the user is logged out
        user = response.wsgi_request.user
        self.assertFalse(user.is_authenticated)

    def test_signup_view(self):
        signup_data = {
            'username': 'newuser',
            'password': 'newpassword123',
            'password_confirm': 'newpassword123',
        }
        response = self.client.post(reverse('question:signup'), data=signup_data)
        self.assertEqual(response.status_code, 302)  # Redirects to 'question:index'
        self.assertRedirects(response, reverse('question:index'))
        
        # Check if the new user is created and authenticated
        user = User.objects.get(username='newuser')
        self.assertIsNotNone(user)
        self.assertTrue(response.wsgi_request.user.is_authenticated)

    def test_signup_password_mismatch(self):
        signup_data = {
            'username': 'newuser',
            'password': 'newpassword123',
            'password_confirm': 'differentpassword',
        }
        response = self.client.post(reverse('question:signup'), data=signup_data)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Passwords do not match')
        
        # Check if the user is not created
        with self.assertRaises(User.DoesNotExist):
            User.objects.get(username='newuser')

간단하게 login기능을 구현해주는 test를 짜고

name: Django Tests

on:
  push:
    branches:
      - test
  pull_request:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'

    - name: Install dependencies
      run: |
        python -m venv venv
        source venv/bin/activate
        pip install django

    - name: Change directory
      run: |
        source venv/bin/activate
        python -m pip install Pillow
        cd SOS
        python manage.py makemigrations question
        python manage.py migrate
        python manage.py test
       

test branch에 push 해줄 때마다 test가 작동되게 yml파일도 썼다

image.png잘 된다!

이제 추가적인 test와 CD에 대해서는 다음 포스트에..

1
0
Jihwan

Jihwan

CI/CD에 대해 공부해보자 - 1. Github Action

학교 팀플을 하던 중.. 웹 사이트를 만들고 배포를 할 일이 생겼는데, 내 머리 속에는 아무것도 들어있지 않아서 무작정 youtube랑 구글을 뒤져서 공부하려고 한다! (참고: 드림코딩 youtube)

일단 CI/CD가 무엇인지 알아보자

  • CI(continuous integration), 지속적 통합: 개발을 할 때, 개개인이 쓴 코드를 주기적으로 merge 한 뒤에 build 와 test를 자동적으로 해주는 것을 말한다. (때문에 코딩을 할 때 개개인이 unit한 부분을 건드려줘야 한다.)

  • CD(continuous deployment), 지속적 배포: CI과정을 마친 코드를 release까지 자동적으로 해주는 것. continuous delivery도 있는데 이건 release 준비까지 자동으로 한 뒤, 사람이 수동으로 확인하는 과정을 거쳐서 배포하는 것을 말한다.

CI/CD의 flow는 이렇다 (출처: 드림코딩 youtube)

image.png

근데 배포가 정확하게 무슨 말일까? 배포는 쉽게 말해서 다른 사용자들이 우리가 개발한 웹 페이지를 인터넷 상에서 접근 가능하게 만드는 것이라고 한다. (ip 주소 할당)

그래서 오늘은 CI/CD를 제공하는 많은 서비스 중에서도, 그래도 평소에 많이 접한 git에서 제공하는 github action을 써보려고 한다. (참고: 어쩐지 오늘은(?), zzsza.github.io)

Github action이란? 일단 공식문서를 뒤져봤다

image.png

그렇다고 한다. 그러면 어떻게 CI/CD가 이루어지는지 보자. (5가지 components)

  • Events: workflows를 실행시키게 되는 event를 말한다. (triggered by)

  • Runners: 실행 환경(virtual machine?, container처럼 동작한다고 한다)

  • Workflows: 내 repository에 .github/workflows에 있는 .yml에 의해 정의되는 configurable automated process, 하나하나의 step인 job을 하나 또는 여러개 실행하게 된다. 하나의 repository가 다양한 workflows를 가질 수 있다.

  • Jobs: workflow의 하나의 step. 각각의 job이 shell script나 github에서 제공하는 action을 실행한다. Jobs의 dependencies도 설정할 수 있는데, default는 parallel(동시에) 실행되는 것이다.

  • Actions: github에서 제공하는 custom application.

image.pngimage.pngimage.pngimage.pngimage.png

아직 yml파일을 한번도 작성해보지 못한 감자로써, 쉽지않은 과정이다.. 이번 팀플은 django를 이용한 웹 사이트 만들기이기 때문에 더 찾아보았는데

python manage.py test

위의 shell 명령어를 job에 넣어주면 된다고 한다! 저게 뭐냐면..

우리가 Django를 쓸 때 test.py에 정의되어 있는데로 테스트를 해주는 명령어라고 한다.

이 포스트는 여기까지로 하고, 다음 포스트에는 직접 해보는 걸로...

2
0
Jihwan

Jihwan

햇감자의 개발자(가 되고싶은) 일지 - 0

나는 왜 살까?

성인이 되고 몇 년 간 학업을 도외시(?)하며 놀았던 나는 어느새 감자가 되어있었다. 학점은 이게 사람인가 싶고... 아는 것도 없고...

나는 꿈이 없었다. 딱히 목표도, 의지도, 욕심도.. 무엇도 부족하게 살았다. 지금 학교도, 작년까지는 내가 있을 곳이 아니라고 느꼈다.

나는 정말 어렸다. 세상엔 내가 모르는 것들이 너무 많았지만, 알려고 하지도, 알고 싶지도 않았다. 우울하고, 무책임하고, 무지했다.

근데.. 올해 초에 갔다 온 영국 연수가 정말 나한테 많은 생각이 들게 했다. 즐거운 것도 즐거웠지만, 새로운 환경이 고여있던 나한테 커다란 전환점이 되었다. 그리고 얼마나 바보 같은 선택을 해왔는지, 정말 뼈저리게 느꼈다.

아무튼.. 그래서 올해는 학교 열심히 다니고 있는데, 다니면서 난 진짜 아무것도 모르는구나.. 너무 많이 느껴서.. 그리고 내가 얼마나 공부를 안하고 살았는지.. 남들 열심히 할 때 놀았으니까 지금부터라도 열심히 해야지 뭐..

진짜 코딩 공부를 바닥부터 시작하는데, 정리를 잘 하는 습관을 들이고 싶어서 여기에 뭐라도 남기려고 한다!

나도 감자를 탈출하는 날이 올까?

5
2

포스트

아직 포스트가 없습니다.