Github Action 실습 (복습)
•
구조도
•
Github Actions
◦
Github에서 제공하는 CI/CD 도구
◦
Repository에서 어떤 이벤트가 발생하면, 특정 작업이 수행되도록 함
•
Github Actions의 구성
◦
Workflow(워크플로)
▪
Github Actions의 상위 개념
▪
YAML 파일로 작성 (Repository > .github/workflows)
◦
Event(이벤트)
▪
Workflow를 실행하게 하는 규칙이나 조건 (Trigger)
▪
on 속성을 통해 정의
◦
Job(잡)
▪
독립된 가상머신 or 컨테이너에서 동작하는 하나의 처리 단위
▪
Job들은 병렬적으로 실행됨
▪
Job은 여러 개의 Step으로 구성
◦
Step(스텝)
▪
여러 단계를 순차적으로 실행하는 작업 순서
▪
직접 작성하거나 github에서 가져와 사용도 가능
•
Github Action work-flow 예제
# WORKFLOW의 이름 지정
name: my-first-workflow
# Event 지정
on: push
# JOB 정의
jobs:
# JOB 이름 지정
my-first-job:
# RUNNER 정의
runs-on: ubuntu-latest
#STEP 정의
steps:
# uses : ACTION을 사용
# GIT에 있는 코드 받아오는 ACTION
- name: my-git-step
uses: actions/checkout@v3
# RUN : 커맨드 명령어 실행
- name: my-cmd-step
run: ls
- name: my-cmd-step
run: echo "Hello World"
Shell
복사
•
AWS 설정
•
EC2 접속 시 참고사항
•
Docker
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
ubuntu@ip-172-31-34-164:~$ sudo usermod -aG docker $USER
ubuntu@ip-172-31-34-164:~$ newgrp docker
ubuntu@ip-172-31-34-164:~$ docker info
Shell
복사
•
Github action 정리
name: FRONT-WORKFLOW
on:
push:
branches: [ "main" ]
jobs:
front-job:
runs-on: ubuntu-latest
steps:
- name: SSH
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
script: |
# 오류 발생 시 스크립트 중단
set -e
# SSH 접속 후 작업 디렉토리로 이동
echo "SSH 접속"
cd 2024-aws-kostagram/front
# 환경 설정 파일 생성
echo "환경 설정 파일 생성"
echo -e "REACT_APP_REST_SERVER=${{ secrets.REACT_APP_REST_SERVER }}" >> .env
echo -e "REACT_APP_SERVER=${{ secrets.REACT_APP_SERVER }}" >> .env
echo -e "REACT_APP_GOOGLE_ID=${{ secrets.REACT_APP_GOOGLE_ID }}" >> .env
echo -e "REACT_APP_GOOGLE_REDIRECT_URI=${{ secrets.REACT_APP_GOOGLE_REDIRECT_URI }}" >> .env
echo -e "REACT_APP_KAKAO_ID=${{ secrets.REACT_APP_KAKAO_ID }}" >> .env
echo -e "REACT_APP_KAKAO_REDIRECT_URI=${{ secrets.REACT_APP_KAKAO_REDIRECT_URI }}" >> .env
# GIT 최신 코드 가져오기
echo "GIT PULL 작업"
git_output=$(sudo git pull origin main 2>&1)
echo "$git_output"
# Docker 빌드
echo "Docker build"
docker compose up -d --build front-image
Shell
복사
Organization 생성
실습
repository 생성
환경 설정 파일 작성 (Github Action)
docker 주소:
https://docs.docker.com/engine/install/ubuntu/
HOST=3.35.219.146
USERNAME=ubuntu
KEY=my-blog
JWT_KEY=mysecrethmsmysecrethmsmysecrethmsmysecret
REST_SERVER="https://dodream.store/api"
GOOGLE_ID=530115480345-84amh51vqf0h8iq9h06o9qu3g8e0s9np.apps.googleusercontent.com
GOOGLE_KEY=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
GOOGLE_REDIRECT_URI=https://dodream.store/oauth/google
KAKAO_ID=a0af8d5965cdea46c2506401963ea186
KAKAO_KEY=SJNpFkzY7dXn7v7D6MrrMmXytWZUPpjn
KAKAO_REDIRECT_URI=https://dodream.store/oauth/kakao
MYSQL_USER=dodream
MYSQL_PASSWORD=1234
MYSQL_DATABASE=rest_blog-db
TZ=Asia/Seoul
SPRING_DATASOURCE_URL=jdbc:mysql://mysql-container:3306/rest_blog-db
SPRING_DATASOURCE_USERNAME=root
YAML
복사
EC2 인스턴스 생성 + 시작
•
네트워크 설정
•
스토리지 구성
•
sudo 없이 명령어 실행
sudo usermod -aG docker $USER
newgrp docker
docker info
Shell
복사
•
git clone
ubuntu@ip-172-31-2-73:~$ git clone https://github.com/2024-kosta-inst/front-hms.git
Cloning into 'front-hms'...
remote: Enumerating objects: 73, done.
remote: Counting objects: 100% (73/73), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 73 (delta 7), reused 73 (delta 7), pack-reused 0 (from 0)
Receiving objects: 100% (73/73), 236.39 KiB | 14.77 MiB/s, done.
Resolving deltas: 100% (7/7), done.
Shell
복사
•
nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html
}
}
Shell
복사
•
Dockerfile
FROM node:20 AS build
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY --from=build /app/build/ /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Shell
복사
name: FRONT-WORKFLOW
on:
push:
branches: [ "main" ]
jobs:
front-job:
runs-on: ubuntu-latest
steps:
- name: SSH
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: $ {{ secrets.KEY }}
script: |
set -e
cd front-hms
echo -e "REACT_APP_REST_SERVER=${{ secrets.REST_SERVER }}" >> .env
echo -e "REACT_APP_GOOGLE_ID=${{ secrets.GOOGLE_ID }}" >> .env
echo -e "REACT_APP_GOOGLE_REDIRECT_URI={{ secrets.GOOGLE_REDIRECT_URI }}" >> .env
echo -e "REACT_APP_KAKAO_ID={{ secrets.KAKAO_ID }}" >> .env
echo -e "REACT_APP_KAKAO_REDIRECT_URI={{ secrets.KAKAO_REDIRECT_URI }}" >> .env
git_output=$(sudo git pull origin main 2>&1)
docker compose up -d --build
Shell
복사
Route53
•
호스팅 영역 > 호스팅 영역 생성
◦
name: dodream.store
•
가비아 > 네임서버 설정
ns-727.awsdns-26.net
ns-1292.awsdns-33.org
ns-382.awsdns-47.com
ns-1848.awsdns-39.co.uk
Shell
복사
•
가비아 인증 진행
•
레코드 생성
Backend 배포 세팅
•
Back 코드 수정
package com.kosta.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
// application.yml 파일의 location 정보 가져오기
@Value("${spring.upload.location}")
private String uploadPath;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedOrigins( "http://3.35.219.146", "http://dodream.store")
.allowedMethods("OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/api/img/**")
.addResourceLocations("file:"+ uploadPath + "\\");
}
}
Shell
복사
package com.kosta.config;
import com.kosta.repository.UserRepository;
import com.kosta.security.*;
import com.kosta.util.TokenUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import java.util.Collections;
import java.util.List;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
private final UserDetailsService userDetailsService;
private final UserRepository userRepository;
private final JwtProperties jwtProperties;
// JWT Provider
private JwtProvider jwtProvider() {
return new JwtProvider(jwtProperties, userDetailsService);
}
private TokenUtils tokenUtils() {
return new TokenUtils(jwtProvider());
}
private JwtAuthenticationService jwtAuthenticationService() {
return new JwtAuthenticationService(tokenUtils(), userRepository);
}
@Bean
// 인증 관리자 (AuthenticationManager) 설정
AuthenticationManager authenticationManager() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(bCryptPasswordEncoder());
return new ProviderManager(authProvider);
}
// 암호화 빈
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// HTTP 요청에 따른 보안 구성
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 경로 권한 설정
http.authorizeHttpRequests(auth ->
// 특정 URL 경로에 대해서는 인증 없이 접근 가능
auth.requestMatchers(
new AntPathRequestMatcher("/api/oauth/**"), // oAuth 처리 (20240919)
new AntPathRequestMatcher("/api/auth/signup"), // 회원가입
new AntPathRequestMatcher("/api/auth/duplicate"), // 이메일 중복체크
new AntPathRequestMatcher("/api/img/**"), // 이미지
new AntPathRequestMatcher("/api/auth/refresh-token"), // 토큰 재발급
new AntPathRequestMatcher("/api/post/**", "GET")
).permitAll()
// AuthController 중 나머지들은 "ADMIN"만 가능
.requestMatchers(
new AntPathRequestMatcher("/api/auth/") // "ADMIN"만 가능
).hasRole("ADMIN")
// 그 밖의 다른 요청들은 인증을 통과한(로그인한) 사용자라면 모두 접근할 수 있도록 한다.
.anyRequest().authenticated()
);
// 무상태성 세션 관리
http.sessionManagement((sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)));
// 특정 경로(로그인)에 대한 필터 추가
http.addFilterBefore(new LoginCustomAuthenticationFilter(authenticationManager(), jwtAuthenticationService()),
UsernamePasswordAuthenticationFilter.class);
// (토큰을 통해 검증할 수 있도록) 필터 추가
http.addFilterBefore(new JwtAuthenticationFilter(jwtProvider()),
UsernamePasswordAuthenticationFilter.class);
// HTTP 기본 설정
http.httpBasic(HttpBasicConfigurer::disable);
// CSRF 비활성화
http.csrf(AbstractHttpConfigurer::disable);
// CORS 비활성화 (나중에 변경)
// http.cors(AbstractHttpConfigurer::disable);
// CORS 설정
http.cors(corsConfig -> corsConfig.configurationSource(corsConfigurationSource()));
return http.getOrBuild();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
return request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedHeaders(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowedOriginPatterns(Collections.singletonList("http://192.168.233.128"));
config.setAllowedOrigins(List.of("http://3.35.219.146", "http://dodream.store"));
config.setAllowCredentials(true);
return config;
};
}
}
Shell
복사
•
Dockerfile
FROM gradle:8-jdk-17-alpine As build
WORKDIR /app
COPY build.gradle settings.gradle gradlew ./
COPY gradle ./gradle
COPY . .
RUN chmod +x gradlew
RUN ./gradlew clean build -x test
FROM openjdk:17-jdk-alpine
WORKDIR /app
COPY /app/build/libs/*.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT [ "java", "-jar", "/app/app.jar" ]
Docker
복사
•
build.gradle
.. 중략 ..
// executable jar 파일만 생성
jar {
enabled = false
}
...
Docker
복사
•
docker-compose.yml
version: '3'
services:
mysql:
image: mysql:8
container_name: mysql-container
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- TZ=${TZ}
ports:
- "3306:3306"
networks:
- server-net
volumes:
- mysql-data:/var/lib/mysql
back:
build:
context: .
container_name: back-container
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_URL}
- SPRING_DATASOURCE_USERNAME=${MYSQL_USER}
- SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD}
networks:
- server-net
depends_on:
- mysql
restart: always
networks:
server-net:
driver: bridge
volumes:
mysql-data:
Docker
복사
•
인증
sudo apt-get install -y certbot python3-certbot-nginx'
ubuntu@ip-172-31-2-73:~$ sudo certbot --nginx -d dodream.store -d www.dodream.store
ubuntu@ip-172-31-2-73:~$ sudo certbot --nginx -d dodream.store -d www.dodream.store
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel): codesche@gmail.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.4-April-3-2024.pdf. You must agree in
order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y
Shell
복사
•
절차 성공 후
ubuntu@ip-172-31-2-73:/etc/letsencrypt$ sudo certbot --nginx -d dodream.store -d www.dodream.store
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for dodream.store and www.dodream.store
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/dodream.store/fullchain.pem
Key is saved at: /etc/letsencrypt/live/dodream.store/privkey.pem
This certificate expires on 2024-12-29.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for dodream.store to /etc/nginx/sites-enabled/default
Successfully deployed certificate for www.dodream.store to /etc/nginx/sites-enabled/default
Congratulations! You have successfully enabled HTTPS on https://dodream.store and https://www.dodream.store
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ubuntu@ip-172-31-2-73:/etc/letsencrypt$ ls
accounts archive cli.ini live options-ssl-nginx.conf renewal renewal-hooks ssl-dhparams.pem
ubuntu@ip-172-31-2-73:/etc/letsencrypt$ cd live
bash: cd: live: Permission denied
ubuntu@ip-172-31-2-73:/etc/letsencrypt$ sudo chown -R $USER:$USER /etc/letsencrypt
ubuntu@ip-172-31-2-73:/etc/letsencrypt$ sudo chmod -R 755 /etc/letsencrypt
ubuntu@ip-172-31-2-73:/etc/letsencrypt$ cd live
ubuntu@ip-172-31-2-73:/etc/letsencrypt/live$ ls
README dodream.store
ubuntu@ip-172-31-2-73:/etc/letsencrypt/live$ cd dodream.store/
ubuntu@ip-172-31-2-73:/etc/letsencrypt/live/dodream.store$ ls
README cert.pem chain.pem fullchain.pem privkey.pem
ubuntu@ip-172-31-2-73:/etc/letsencrypt/live/dodream.store$ sudo chmod 644 /etc/letsencrypt/live/dodream.store/fullchain.pem
ubuntu@ip-172-31-2-73:/etc/letsencrypt/live/dodream.store$ sudo chmod 644 /etc/letsencrypt/live/dodream.store/privkey.pem
Shell
복사
•
HTTPS 인증 위한 nginx.conf 수정
server {
# HTTP 요청이 들어오면
listen 80;
server_name dodream.store www.dodream.store;
location / {
# 모든 요청을 HTTPS로 리디렉션
return 301 https://$host$request_uri;
}
}
server {
# 443 포트에서 SSL(HTTPS) 요청을 처리
listen 443 ssl;
server_name dodream.store www.dodream.store;
# SSL 인증서 경로 설정
ssl_certificate /etc/ssl/certs/fullchain.pem;
ssl_certificate /etc/ssl/certs/privkey.pem;
# SSL 설정
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers HIGH:!aNULL:!MD5;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $ruri/ /index.html;
}
}
Shell
복사
구성도
도메인 주소변경
•
Louter53 설정 변경
•
Nginx 설정 변경