들어가며

이번 주에는 GitHub Actions를 이용한 CI/CD에 대해 알아보겠습니다.

GitHub Actions CI/CD

GitHub Actions란?

  • 공식문서 - 개요 - 요금 - GitHub Actions의 다양한 기능 활용하기
  • GitHub Actions는 GitHub에서 호스팅되는 지속적 통합(CI) 및 지속적 배포(CD) 서비스입니다.
  • 푸시나 풀 리퀘스트와 같은 이벤트에 반응하여 사용자 지정된 워크플로를 실행할 수 있습니다. 이를 통해 CI/CD를 GitHub Actions로 구현할 수 있습니다.
  • GitHub Actions는 GitHub Marketplace에서 기존에 작성된 워크플로를 가져와 사용할 수 있습니다. Slack 알림, AWS 배포 등 다양한 워크플로가 있습니다.
  • GitHub Actions는 YAML 파일을 사용하여 워크플로를 정의합니다. 워크플로는 job으로 구성되며, 각 job은 step으로 구성됩니다. img.png
  • GitHub Actions의 요금은 아래와 같으며 500MB 스토리지와 매월 2000분을 무료로 사용할 수 있습니다.

    계획 스토리지 분(월)
    GitHub Free 500 MB 2,000
    GitHub Pro 1GB 3,000
    조직용 GitHub Free 500 MB 2,000
    GitHub Team 2GB 3,000
    GitHub Enterprise Cloud 50GB 50,000

첫 GitHub Actions 워크플로 만들기

  • GitHub Actions는 repository 내의 .github/workflows 디렉터리에 저장된 워크플로우 정의 파일을 읽어서 실행합니다.
  • 간단한 GitHub Repository를 만들고 그 안에 워크플로우를 만들어서 테스트 해보겠습니다.
    1. 새로운 Repository 만들기 링크 img.png
    2. git clone으로 repository를 로컬로 가져옵니다.
      $ git clone https://github.com/sweetlittlebird/2024-cicd-w2-1.git
      $ cd 2024-cicd-w2-1
      
    3. .github/workflows 디렉터리를 만들고 그 안에 main.yml 파일을 만듭니다. (파일명은 자유롭게 지정 가능합니다.)
      $ mkdir -p .github/workflows
      $ touch .github/workflows/main.yml
      
    4. main.yml 파일에 워크플로우 작성합니다.
      # .github/workflows/main.yml
      name: Hello World                 # Github 웹 사이드바 이름
      on:
        workflow_dispatch:              # 수동으로 실행할 수 있는 워크플로우
        push:                           # push 이벤트 발생 시 실행
           
      jobs:
        build:                          # Jobs 이름
          runs-on: ubuntu-latest        # 실행 환경
          steps:                        # Job 내부의 Step 들 
            - uses: actions/checkout@v3 # actions/checkout 레포지토리의 v3 버전의 워크플로우 사용
            - name: Hello World         # Step 이름
              run: echo "Hello World"     
      
    5. GitHub에 push하여 워크플로우를 실행합니다.
      $ git add .
      $ git commit -m "GitHub Actions 추가"
      $ git push origin main
      
    6. GitHub Repository에서 Actions 탭을 클릭하여 워크플로우를 확인합니다. 링크 img.png img.png
  • 워크플로우가 잘 실행되어서 Hello World가 잘 출력되었습니다.

워크플로우를 수동으로 트리거 하기

  • 앞서 작성한 워크플로우 처럼 on: workflow_dispatch를 사용하면 워크플로우를 수동으로 실행할 수 있습니다.
  • GitHub Repository에서 Actions 탭을 클릭하고 Run workflow 버튼을 클릭하여 워크플로우를 수동으로 실행할 수 있습니다. img.png

직접 개발 후 실행

  • 이번에는 가상머신을 만들고 그 안에서 간단한 웹 서버를 만들어서 GitHub Actions로 배포해보겠습니다.
  • 가상머신은 Vagrant를 사용하여 만들어 보겠습니다.
    # Vagrantfile
    Vagrant.configure("2") do |config|
      config.vm.box = "ubuntu/jammy64"
      config.vm.network "forwarded_port", guest: 22, host: 20022
      config.vm.network "forwarded_port", guest: 80, host: 20080
      config.vm.provision "shell", inline: <<-SHELL
        sudo apt-get update
        sudo apt install -y tree 
      SHELL
    end
    
  • 위와 같이 Vagrantfile을 작성하고 vagrant up으로 가상머신을 만들어 줍니다.

    $ vagrant up
      
    $ vargrant ssh 
    # --------
      
    $ python3 -V
    # => Python 3.10.12
    $ cat > server.py <<EOF
    from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
    from datetime import datetime
      
    class RequestHandler(BaseHTTPRequestHandler):
        def do_GET(self):
            self.send_response(200)
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            now = datetime.now()
            response_string = now.strftime("The time is %-I:%M:%S %p, CloudNeta Study.\n")
            self.wfile.write(bytes(response_string, "utf-8")) 
      
    def startServer():
        try:
            server = ThreadingHTTPServer(('', 80), RequestHandler)
            print("Listening on " + ":".join(map(str, server.server_address)))
            server.serve_forever()
        except KeyboardInterrupt:
            server.shutdown()
      
    if __name__== "__main__":
        startServer()
    EOF
      
    $ sudo python3 server.py
      
    # 아래 확인 후
    # CTRL+C 로 실행 취소
      
    # (신규터미널) 서버1 SSH 접속
    $ curl localhost
    # => The time is 2:50:56 PM, CloudNeta Study.
    $ sudo ss -tnlp
    # => State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
    #    LISTEN 0      5            0.0.0.0:80        0.0.0.0:*    users:((&quot;python3&quot;,pid=2299,fd=3))
    #    LISTEN 0      128          0.0.0.0:22        0.0.0.0:*    users:((&quot;sshd&quot;,pid=1030,fd=3))
      
    # --------
    
  • Git 작업
    • 토큰 발급해두기 : scope (repo, workflow) 필요합니다. img.png
    • Private Repo 신규 생성 img.png
    • 가상머신에서 Git 작업
      $ GITUSER="sweetlittlebird"
      $ git config --global user.name $GITUSER
      $ git config --global user.email "sweetlittlebird@sweetlittlebird.com"
      $ git clone https://github.com/$GITUSER/2024-cicd-w2-2.git
      # => Cloning into '2024-cicd-w2-2'...
      #    Username for 'https://github.com': git
      #    Password for 'https://git@github.com':
      #    remote: Enumerating objects: 3, done.
      #    remote: Counting objects: 100% (3/3), done.
      #    remote: Compressing objects: 100% (2/2), done.
      #    remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
      #    Receiving objects: 100% (3/3), done.
      $ cp server.py 2024-cicd-w2-2/
      $ cd 2024-cicd-w2-2
          
      $ git status
      # => On branch main
      #    Your branch is up to date with 'origin/main'.
      #    
      #    Untracked files:
      #      (use &quot;git add &lt;file&gt;...&quot; to include in what will be committed)
      #            server.py
      #    
      #    nothing added to commit but untracked files present (use &quot;git add&quot; to track)
      $ git add .
      $ git commit -m "Initial commit"
      # => [main 30500ca] Initial commit
      #     1 file changed, 22 insertions(+)
      #     create mode 100644 server.py
      $ git push origin main 
      # => Username for 'https://github.com': git
      #    Password for 'https://git@github.com': *** # <span style="color: green;">👉 ghp로 시작하는 발급해둔 토큰을 사용합니다.</span> 
      #    Enumerating objects: 4, done.
      #    Counting objects: 100% (4/4), done.
      #    Delta compression using up to 2 threads
      #    Compressing objects: 100% (3/3), done.
      #    Writing objects: 100% (3/3), 665 bytes | 332.00 KiB/s, done.
      #    Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
      #    To https://github.com/sweetlittlebird/2024-cicd-w2-2.git
      #       ceaed76..30500ca  main -&gt; main
      

      img.png

  • 서버 실행
    $ nohup sudo python3 server.py > server.log 2>&1 &
    $ cat server.log
    # => nohup: ignoring input
    $ curl localhost
    # => The time is 3:13:00 PM, CloudNeta Study.
    $ cat server.log
    # => nohup: ignoring input
    #    127.0.0.1 - - [14/Dec/2024 15:13:00] &quot;GET / HTTP/1.1&quot; 200 -
      
    #
    $ grep log .gitignore
    # => # Installer logs
    #    pip-log.txt
    #    *.log          # <span style="color: green;">👉 로그 파일은 git에서 무시하도록 되어있습니다.</span>
      
    #
    $ git add .
    $ git commit -m "add log file"
    # => nothing to commit, working tree clean  <span style="color: green;">👉 로그 파일은 git에서 무시하도록 되어있어서 git add . 의 영향을 받지 않습니다.</span>
    $ git status
    # => nothing to commit, working tree clean
    
  • 코드 수정 후 재실행
    #
    $ sed -i "s/CloudNeta/CICD/g" server.py
      
    # 프로세스 종료
    $ sudo ss -tnlp
    # => State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
    #    LISTEN 0      128          0.0.0.0:22        0.0.0.0:*    users:(("sshd",pid=883,fd=3))
    #    LISTEN 0      5            0.0.0.0:80        0.0.0.0:*    users:((&quot;python3&quot;,pid=2287,fd=3))
    $ sudo fuser -k -n tcp 80                 # <span style="color: green;">👉 80번 포트를 사용하는 프로세스를 종료합니다.</span>
    # => 80/tcp:               2287
    #    [1]+  Killed                  nohup sudo python3 server.py &gt; server.log 2&gt;&amp;1  (wd: ~)
    #    (wd now: ~/2024-cicd-w2-2)
    $ sudo ss -tnlp
    # => State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
    #    LISTEN 0      128          0.0.0.0:22        0.0.0.0:*    users:(("sshd",pid=883,fd=3))
    # <span style="color: green;">👉 80 포트를 사용하던 python3가 종료되어서 없어졌습니다.</span>
      
    # 재실행
    $ nohup sudo python3 server.py > server.log 2>&1 &
    $ curl localhost
    # => The time is 3:24:23 PM, CICD Study.  # <span style="color: green;">👉 수정사항이 반영되었습니다.</span>
    
  • 코드 push
    # 매번 사용자 인증을 요구하지 않도록 인증 정보를 저장하도록 설정하겠습니다.
    $ git config --global credential.helper store
      
    #
    $ git add . && git commit -m "version update" && git push origin main
    # => [main 11f7a3d] version update
    #     1 file changed, 1 insertion(+), 1 deletion(-)
    #    Username for 'https://github.com': git
    #    Password for 'https://git@github.com':
    #    Enumerating objects: 5, done.
    #    Counting objects: 100% (5/5), done.
    #    Delta compression using up to 2 threads
    #    Compressing objects: 100% (3/3), done.
    #    Writing objects: 100% (3/3), 312 bytes | 156.00 KiB/s, done.
    #    Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
    #    remote: Resolving deltas: 100% (1/1), completed with 1 local object.
    #    To https://github.com/sweetlittlebird/2024-cicd-w2-2.git
    #       30500ca..11f7a3d  main -&gt; main
      
    $ git push origin main
    # => Everything up-to-date
    # <span style="color: green;">👉 인증정보가 저장되어서 별도의 인증과정 없이 사용할 수 있습니다.</span>
    

GitHub Actions로 배포하기

  • 개인 PC에서 작업 후 Github에 push하면 GitHub Actions를 통해 VM에 배포가 되도록 CI/CD를 구성해보겠습니다.
SSH 키 생성
  • 먼저 가상머신에 ssh 접속을 위해 ssh key를 생성하고 GitHub에 등록합니다.
    $ cd ~/.ssh
    $ ssh-keygen -t ed25519
    # => Enter file in which to save the key (/home/vagrant/.ssh/id_ed25519):
    #    Enter passphrase (empty for no passphrase): # <span style="color: green;">👉 비밀번호 없이 엔터만 입력합니다.</span>
    #    Enter same passphrase again:                # <span style="color: green;">👉 엔터만 입력합니다.</span>
      
    # 생성된 공개키를 로그인 허용 키 목록에 추가합니다
    $ cat id_ed25519.pub  >> authorized_keys     
    $ cat id_ed25519   # <span style="color: green;">👉 개인키를 복사합니다. 복사한 키는 github secret에 추가할 것입니다.</span>
    
GitHub에 SSH 키와 가상머신 외부 IP 등록
  • GitHub의 Repository에서 Settings -> Secrets and variables -> Actions -> New repository secret를 클릭하여 아래와 같이 추가합니다.
    • SSH_PRIVATE_KEY : 앞에서 복사한 개인키를 추가합니다. img.png
    • EC2_PIP : 가상머신의 외부 IP를 추가합니다. (인터넷에서 접속가능한 IP여야 합니다.) img.png
내 PC에서 코드 작업
  • 내 PC에서 작업을 합니다.

    $ git clone https://github.com/sweetlittlebird/2024-cicd-w2-2.git
    # => Cloning into '2024-cicd-w2-2'...
    #    remote: Enumerating objects: 9, done.
    #    remote: Counting objects: 100% (9/9), done.
    #    remote: Compressing objects: 100% (7/7), done.
    #    remote: Total 9 (delta 1), reused 6 (delta 1), pack-reused 0 (from 0)
    #    Receiving objects: 100% (9/9), done.
    #    Resolving deltas: 100% (1/1), done.
    $ cd 2024-cicd-w2-2
      
    # 워크플로우 파일 생성
    $ mkdir -p .github/workflows/
    $ touch .github/workflows/deploy.yaml
      
    # 소스 수정
    $ sed -i -e "s/CICD/CICD 2w/g" server.py
    
  • .github/workflows/deploy.yaml 파일을 작성합니다.

    # .github/workflows/deploy.yaml
    name: CICD1
    on:
      workflow_dispatch:
      push:
        branches:
          - main
      
    jobs:
      deploy:
        runs-on: ubuntu-latest
        steps:
          - name: Configure the SSH Private Key Secret
            run: |
              mkdir -p ~/.ssh/
              echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
              chmod 600 ~/.ssh/id_rsa
      
          - name: Set Strict Host Key Checking
            run: echo "StrictHostKeyChecking=no" > ~/.ssh/config
      
          - name: Git Pull
            run: |
              export MY_HOST="${{ secrets.EC2_PIP }}"
              ssh vagrant@$MY_HOST << EOF
                cd /home/vagrant/2024-cicd-w2-2 || exit 1
                git pull origin main || exit 1
              EOF
      
          - name: Run service
            run: |
              export MY_HOST="${{ secrets.EC2_PIP }}"
              ssh vagrant@$MY_HOST sudo fuser -k -n tcp 80 || true
              ssh vagrant@$MY_HOST "nohup sudo -E python3 /home/vagrant/2024-cicd-w2-2/server.py > /home/vagrant/2024-cicd-w2-2/server.log 2>&1 &"
    
  • 코드를 push합니다.

    $ git add . && git commit -m "add workflow" && git push origin main
    
  • GitHub Actions를 확인해보겠습니다. img.png
  • 가상머신에서 확인해보겠습니다.
    $ vagrant ssh
      
    #-------
    $ cd 2024-cicd-w2-2/
    $ grep -i cicd server.py
    # =>         response_string = now.strftime(&quot;The time is %-I:%M:%S %p, CICD 2w Study.\n&quot;)
    $ sudo ps -ef |grep server.py
    # => root        2620       1  0 15:57 ?        00:00:00 sudo -E python3 /home/vagrant/2024-cicd-w2-2/server.py
    #    root        2621    2620  0 15:57 ?        00:00:00 python3 /home/vagrant/2024-cicd-w2-2/server.py
    $ tail /home/vagrant/2024-cicd-w2-2/server.log
    # => nohup: ignoring input
    #------- 
    
  • 변경사항을 브라우저로 확인해보겠습니다. img.png
    • 변경사항이 잘 반영되어서 CICD 2w로 변경되었습니다.
코드 수정 후 동작 확인
  • 개인 PC에서 코드와 워크플로우를 수정하고 GitHub에 push하여 배포가 잘 되는지 확인해보겠습니다.
    • 코드 수정
      $ sed -i -e "s/CICD 2w/CICD1 End/g" server.py
      
    • 워크플로우 수정

      # .github/workflows/deploy.yaml
      name: CICD1 End
      on:
        workflow_dispatch:
        push:
          branches:
            - main
          
      jobs:
        deployfinal:
          runs-on: ubuntu-latest
          steps:
            - name: Configure the SSH Private Key Secret
              run: |
                mkdir -p ~/.ssh/
                echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
                chmod 600 ~/.ssh/id_rsa
          
            - name: Set Strict Host Key Checking
              run: echo "StrictHostKeyChecking=no" > ~/.ssh/config
          
            - name: Git Pull
              run: |
                export MY_HOST="${{ secrets.EC2_PIP }}"
                ssh vagrant@$MY_HOST << EOF
                  cd /home/vagrant/2024-cicd-w2-2 || exit 1
                  git pull origin main || exit 1
                EOF
          
            - name: Run service
              run: |
                export MY_HOST="${{ secrets.EC2_PIP }}"
                ssh vagrant@$MY_HOST sudo fuser -k -n tcp 80 || true
                ssh vagrant@$MY_HOST "nohup sudo -E python3 /home/vagrant/2024-cicd-w2-2/server.py > /home/vagrant/2024-cicd-w2-2/server.log 2>&1 &"
      
    • 코드 push
      #
      $ git add . && git commit -m "edit workflow" && git push origin main
          
      # [가상머신]
      $ grep -i cicd server.py
      # =>         response_string = now.strftime(&quot;The time is %-I:%M:%S %p, CICD1 End Study.
      # <span style="color: green;">👉 변경된 소스가 서버에 반영되었습니다.</span>
          
      $ sudo ps -ef |grep server.py
      # => root        3011       1  0 16:17 ?        00:00:00 sudo -E python3 /home/vagrant/2024-cicd-w2-2/server.py
      #    root        3012    3011  0 16:17 ?        00:00:00 python3 /home/vagrant/2024-cicd-w2-2/server.py
      #    vagrant     3035    2255  0 16:18 pts/0    00:00:00 grep --color=auto server.py
      $ tail /home/vagrant/2024-cicd-w2-2/server.log
      
  • 변경사항을 브라우저로 확인해보겠습니다. img.png
    • 변경사항이 잘 반영되어서 CICD1 End로 변경되었습니다.

GitHub Actions의 Marketplace 활용하기

  • 이번에는 GitHub Actions의 Marketplace에서 다른 액션을 가져와서 사용해보겠습니다.
  • Marketplace는 https://github.com/marketplace로 접속할 수 있습니다.
  • 이번 실습에서 사용할 액션은 ssh와 scp 입니다.

SSH for GitHub Actions

  • 관련 주소 : Github, marketplace
  • SSH for GitHub Actions는 원격 서버에 SSH로 접속하여 명령을 실행할 수 있습니다.
  • 앞의 실습처럼 쉘 명령으로 ssh 접속이 가능하지만 이 액션을 사용하면 더 간편하게 사용할 수 있습니다.
  • 사용 예
    1. 아이디/비밀번호로 원격 서버에 접속하여 whoami 명령을 실행합니다.

      name: remote ssh command
      on: [push]
      jobs:
        build:
          name: Build
          runs-on: ubuntu-latest
          steps:
            - name: executing remote ssh commands using password
              uses: appleboy/ssh-action@v1.2.0
              with:
                host: ${{ secrets.HOST }}
                username: linuxserver.io
                password: ${{ secrets.PASSWORD }}
                port: ${{ secrets.PORT }}
                script: whoami
      
    2. 비밀번호 대신 private key를 사용하여 로그인 합니다.

      # Using private key
      - name: executing remote ssh commands using ssh key
        uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          script: whoami
      
    3. 여러 명령을 실행할 수 있습니다.

      # Multiple Commands
      - name: multiple command
        uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          script: |
            whoami
            ls -al
      
    4. 환경 변수를 쉘에 전달할 수 있습니다.

      # Pass environment variable to shell script
      - name: pass environment
        uses: appleboy/ssh-action@v1.2.0
        env:
          FOO: "BAR"
          BAR: "FOO"
          SHA: ${{ github.sha }}
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          envs: FOO,BAR,SHA
          script: |
            echo "I am $FOO"
            echo "I am $BAR"
            echo "sha: $SHA"
      
  • 워크플로우 설정 후 테스트

    # .github/workflows/ssh-deploy.yaml
    name: CICD2
    on:
      workflow_dispatch:
      push:
        branches:
          - main
      
    jobs:
      ssh-deploy:
        runs-on: ubuntu-latest
        steps:
          - name: Github Repository Checkout
            uses: actions/checkout@v4
      
          - name: executing remote ssh commands
            uses: appleboy/ssh-action@v1.2.0
            env:
              AWS_KEYS: |
                ACCESSKEY : asdf1234end
                SECRETKEY : qwer1234end
            with:
              host: ${{ secrets.EC2_PIP }}
              port: 22
              username: vagrant
              key: ${{ secrets.SSH_PRIVATE_KEY }}
              envs: AWS_KEYS
              script_stop: true
              script: |
                cd /home/vagrant/2024-cicd-w2-2
                echo "$AWS_KEYS" > .env
    
    $ git add . && git commit -m "add ssh action test" && git push origin main
    
  • 가상머신에서 확인

    $ ls -al ~/2024-cicd-w2-2/
    # => total 32
    #    drwxrwxr-x 4 vagrant vagrant 4096 Oct 01 16:46 .
    #    drwxr-x--- 5 vagrant vagrant 4096 Oct 01 16:46 ..
    #    -rw-rw-r-- 1 vagrant vagrant   49 Oct 01 16:46 .env  # <span style="color: green;">👉 .env파일이 생성되었습니다.</span>  
    #    drwxrwxr-x 8 vagrant vagrant 4096 Oct 01 16:46 .git
    #    drwxrwxr-x 3 vagrant vagrant 4096 Oct 01 15:57 .github
    #    -rw-rw-r-- 1 vagrant vagrant 3139 Oct 01 15:11 .gitignore
    #    -rw-rw-r-- 1 vagrant vagrant    0 Oct 01 16:46 server.log
    #    -rw-rw-r-- 1 vagrant vagrant  761 Oct 01 16:17 server.py
    #    -rw-rw-r-- 1 vagrant vagrant  759 Oct 01 16:17 server.py-e
    $ cat ~/2024-cicd-w2-2/.env
    # => ACCESSKEY : asdf1234end
    #    SECRETKEY : qwer1234end
    
  • GitHub에서 .env가 있는지 확인해보겠습니다. img.png .env 파일이 Git에는 없습니다! 하지만 가상머신 서버에 .env 파일이 있었던 것은 SSH for GitHub Actions를 통해 전달된 것입니다.

SCP for GitHub Actions

  • 관련 주소 : Github, marketplace
  • SCP for GitHub Actions는 원격 서버로 파일을 전송해주는 액션입니다.
  • 실습을 통해 기능을 알아보겠습니다.
  • server.py 수정하기
    $ sed -i -e "s/CICD1 End Study/SCP Test/g" server.py
    
  • 워크플로우 파일 수정하기

    # .github/workflows/scp-ssh-deploy.yaml
    name: CICD2
    on:
      workflow_dispatch:
      push:
        branches:
          - main
      
    jobs:
      scp-ssh-deploy:
        runs-on: ubuntu-latest
        steps:
          - name: Github Repository Checkout
            uses: actions/checkout@v4
      
          - name: executing remote ssh commands
            uses: appleboy/ssh-action@v1.2.0
            env:
              AWS_KEYS: ${{ secrets.MYKEYS }}
            with:
              host: ${{ secrets.EC2_PIP }}
              username: vagrant
              port: 22
              key: ${{ secrets.SSH_PRIVATE_KEY }}
              envs: AWS_KEYS
              script_stop: true
              script: |
                 cd /home/vagrant/2024-cicd-w2-2
                 echo "$AWS_KEYS" > .env
                 sudo fuser -k -n tcp 80 || true
      
          - name: copy file via scp
            uses: appleboy/scp-action@v0.1.7
            with:
              host: ${{ secrets.EC2_PIP }}
              username: vagrant
              port: 22
              key: ${{ secrets.SSH_PRIVATE_KEY }}
              source: server.py
              target: /home/vagrant/2024-cicd-w2-2
    
  • 코드 push하기
    $ git add . && git commit -m "using scp ssh action" && git push origin main
    
  • 가상서버에서 확인
    # 서버 1
    $ ls -al ~/2024-cicd-w2-2/
    # => total 28
    #    drwxrwxr-x 4 vagrant vagrant 4096 Dec 14 17:04 .
    #    drwxr-x--- 5 vagrant vagrant 4096 Dec 14 17:04 ..
    #    -rw-rw-r-- 1 vagrant vagrant    1 Dec 14 17:03 .env
    #    drwxrwxr-x 8 vagrant vagrant 4096 Dec 14 17:02 .git
    #    drwxrwxr-x 3 vagrant vagrant 4096 Dec 14 15:57 .github
    #    -rw-rw-r-- 1 vagrant vagrant 3139 Dec 14 15:11 .gitignore
    #    -rw-rw-r-- 1 vagrant vagrant    0 Dec 14 17:02 server.log
    #    -rw-r--r-- 1 vagrant vagrant  754 Dec 14 17:03 server.py
    $ cat ~/2024-cicd-w2-2/server.py | grep SCP
    # =>         response_string = now.strftime(&quot;The time is %-I:%M:%S %p, SCP Test.\n&quot;)  
    # <span style="color: green;">👉 변경사항이 잘 반영되었습니다.</span>
    

최종 실습

  • 앞서 쉘 명령으로 진행했던 작업을 GitHub Actions의 Marketplace에서 가져온 ssh, scp 액션을 사용하여 진행해보겠습니다.
  • 내 PC에서 소스 수정하기
    $ sed -i -e "s/SCP Test/CICD2 End 🥳/g" server.py
    
  • 워크플로우 수정하기

    # .github/workflows/deploy.yaml
    name: CICD2
    on:
      workflow_dispatch:
      push:
        branches:
          - main
      
    jobs:
      deploy:
        runs-on: ubuntu-latest
        steps:
          - name: Github Repository Checkout
            uses: actions/checkout@v4
      
          - name: copy file via ssh
            uses: appleboy/scp-action@v0.1.7
            with:
              host: ${{ secrets.EC2_PIP }}
              username: vagrant
              port: 22  
              key: ${{ secrets.SSH_PRIVATE_KEY }}
              source: server.py
              target: /home/vagrant
      
          - name: executing remote ssh commands 
            uses: appleboy/ssh-action@v1.2.0
            env:
              AWS_KEYS: ${{ secrets.MYKEYS }}
            with:
              host: ${{ secrets.EC2_PIP }}
              username: vagrant
              port: 22
              key: ${{ secrets.SSH_PRIVATE_KEY }}
              envs: AWS_KEYS
              script_stop: true
              script: |
                 cd /home/vagrant/2024-cicd-w2-2
                 echo "$AWS_KEYS" > .env
                 sudo fuser -k -n tcp 80 || true
                 rm server.py
                 cp /home/vagrant/server.py ./
                 nohup sudo -E python3 /home/vagrant/2024-cicd-w2-2/server.py > /home/vagrant/2024-cicd-w2-2/server.log 2>&1 &
                 echo "test" >> /home/vagrant/text.txt
    
  • 코드 push하기
    $ git add . && git commit -m "Deploy CICD2 Final" && git push origin main
    
  • 웹으로 접속해서 확인해보겠습니다. img.png
    • 변경사항이 잘 반영되어서 CICD2 End 🥳로 변경되었습니다.

GitHub Actions with Ansible

  • 이번에는 GitHub Actions를 사용하여 Ansible을 실행해보겠습니다.
  • 상세한 Ansible 사용법은 Ansible 공식문서를 참고하시고, 이번 실습에서는 간단하게 Ansible을 이용해서 Ping 테스트를 해보겠습니다. (여기에서의 ping은 ansible이 해당 호스트와 통신이 가능한지 확인하는 것이고 일반적인 ping과는 다릅니다.)
  • 워크플로우 작성

    # .github/workflows/ansible-deploy.yaml
    name: Run Ansible
    on:
      workflow_dispatch:
      push:
        branches:
          - main
      
    jobs:
      run-playbooks:
        runs-on: ubuntu-latest
        steps:
          - name: Github Repository Checkout
            uses: actions/checkout@v4
      
          - name: Setup Python 3
            uses: actions/setup-python@v5
            with:
              python-version: "3.8"
      
          - name: Upgrade Pip & Install Ansible
            run: |
              python -m pip install --upgrade pip
              python -m pip install ansible
      
          - name: Implement the Private SSH Key
            run: |
              mkdir -p ~/.ssh/
              echo "$" > ~/.ssh/id_rsa
              chmod 600 ~/.ssh/id_rsa
      
          - name: Ansible Inventory File for Remote host
            run: |
              mkdir -p ./devops/ansible/
              export INVENTORY_FILE=./devops/ansible/inventory.ini
              echo "[my_host_group]" > $INVENTORY_FILE
              echo "$" >> $INVENTORY_FILE
      
          - name: Ansible Default Configuration File
            run: |
              mkdir -p ./devops/ansible/
              cat <<EOF > ./devops/ansible/ansible.cfg
              [defaults]
              ansible_python_interpreter = '/usr/bin/python3'
              ansible_ssh_private_key_file = ~/.ssh/id_rsa
              remote_user = vagrant
              inventory = ./inventory.ini
              host_key_checking = False
              EOF
      
          - name: Ping Ansible Hosts
            working-directory: ./devops/ansible/
            run: |
              ansible all -m ping
      
          # - name: Run Ansible Playbooks
          #   working-directory: ./devops/ansible/
          #   run: |
          #     ansible-playbook install-nginx.yaml
      
          # - name: Deploy Python via Ansible
          #   working-directory: ./devops/ansible/
          #   run: |
          #     ansible-playbook deploy-python.yaml
    
  • 워크플로우 push 하기
    $ git add . && git commit -m "Deploy Ansible Test" && git push origin main
    
  • GitHub Actions에서 ping 결과를 확인해보겠습니다. img.png
    • 정상적으로 ping이 가서 “pong”이라는 응답이 돌아왔습니다.

마치며

지금까지 GitHub Actions의 기본적인 사용하고, Marketplace에서 제공하는 액션 사용해보고, Ansible까지 맛보기로 사용해보았습니다.

GitHub Actions는 일부 제약이 있지만 무료로 편리하게 사용할 수 있고 Marketplace를 통해 다양한 추가기능을 손쉽게 사용할 수 있어서 매력적입니다.

하지만 매번 가상환경을 프로비저닝하고, 필요한 패키지를 설치하는 등의 작업이 시간이 의외로 많이 들어서, 실제 동작 시간이 예상보다 오래 걸릴 수 있다는 점이 있습니다. 또한 Jenkins나 다른 CI/CD 도구들은 실패한 상태의 쉘에 직접 로그인해서 트러블 슈팅을 할 수 있지만, Github Actions는 그렇게 할 수 없는 점도 아쉽습니다.

그래도 GitHub를 사용하면서 저렴한 가격으로 간단하게 CI/CD를 구성할 수 있다는 점은 매력적인것 같습니다.

추운 날씨에도 애써주신 모든 분들 고생 많으셨습니다. 감사합니다! :bow: