๋ณธ ๋ฌธ์์์๋ AWS์ ์น ์๋ฒ ๊ตฌ๋์ฉ ํด๋ผ์ฐ๋ ์๋น์ค์ธ S3์ WAS ๊ตฌ๋์ฉ ํด๋ผ์ฐ๋ ์๋น์ค์ธ EC2์ ์๋ฒ ์ฐ๋ ์ค ๋ฐ์ํ๋ ๋ฌธ์ ์ํฉ์ ๋ํด ๋ช
ํํ ํ์
ํ๊ณ ํ๊ฒฝ ์ธํ
์ ๋ํ ๋ด์ฉ์ ์ ๋ฆฌํ๊ณ ์ ํ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก CI/CD์ ๋ํ ๋ด์ฉ์ ํฌํจํ๊ณ ์๋ค.
๋ณธ ๋ฌธ์๋ GitHub Action์ ํตํ CI/CD๋ฅผ ๋ค๋ฃจ๋ฉฐ,
๋ํ, ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ด์ฉํ์ฌ S3 ํธ์คํธ๋ก๋ถํฐ ๋ค์ด์จ ์์ฒญ์ EC2๊ฐ ๋ฐ์๋ค์ผ ์ ์๋๋กํ๋ CORS ์ค์ ๋ฒ์ ๋ค๋ค๋ณธ๋ค.
SecurityConfiguration
ํด๋์ค
- ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ ๋ค๋ฅธ Origin์ผ๋ก๋ถํฐ ์์ฒญ์ ํ์ฉํ๊ณ ์ CORS๋ฅผ ๋ค๋ฃจ๊ฒ ๋๋ค.
์์ ํ ํธ์คํธ๋ก๋ถํฐ ์ ๊ณต๋๋ ์์ฒญ์ ํ์ฉํ์ฌ ๋ณด์์ฑ๊ณผ ๊ธฐ๋ฅ์ฑ์ ์ ์ ํ ๊ท ํ์ ์ ์ฐพ์์ผ ํ๋ค.
package com.codestates.stackoverflowbe.global.auth.config;
์ํฌํธ ์๋ต
@Configuration
@EnableWebSecurity // Spring Security๋ฅผ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ์ ์ฉ
public class SecurityConfiguration {
// ํ๋ ๋ฐ ์์ฑ์ ์ค๋ต ...
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.headers().frameOptions().sameOrigin() // (ํด๋น ์ต์
์ ํจํ ๊ฒฝ์ฐ h2์ฌ์ฉ๊ฐ๋ฅ) SOP ์ ์ฑ
์ ์ง, ๋ค๋ฅธ ๋๋ฉ์ธ์์ iframe ๋ก๋ ๋ฐฉ์ง
.and()
โญ.cors(Customizer.withDefaults()) //CORS ์ฒ๋ฆฌํ๋ ๊ฐ์ฅ ์ฌ์ด ๋ฐฉ๋ฒ์ธ CorsFilter ์ฌ์ฉ, CorsConfigurationSource Bean์ ์ ๊ณต
โญ.cors(configuration -> configuration
.configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues()))
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // ์ธ์
์ ๋ณด ์ ์ฅX
.and()
.formLogin().disable() // CSR ๋ฐฉ์์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ formLogin ๋ฐฉ์ ์ฌ์ฉํ์ง ์์
.httpBasic().disable() // UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter ๋ฑ ๋นํ์ฑํ
.exceptionHandling() // ์์ธ์ฒ๋ฆฌ ๊ธฐ๋ฅ
.authenticationEntryPoint(new AccountAuthenticationEntryPoint()) // ์ธ์ฆ ์คํจ์ ์ฒ๋ฆฌ (UserAuthenticationEntryPoint ๋์)
.accessDeniedHandler(new AccountAccessDeniedHandler()) //์ธ๊ฐ ๊ฑฐ๋ถ์ UserAccessDeniedHandler๊ฐ ์ฒ๋ฆฌ๋๋๋ก ์ค๊ณ
.and()
.apply(new CustomFilterConfigurer()) // ์ปค์คํฐ๋ง์ด์งํ ํํฐ ์ถ๊ฐ
.and() // ํ์ฉ๋๋ HttpMethod์ ์ญํ ์ค์
.authorizeHttpRequests( authorize -> authorize
.antMatchers(HttpMethod.GET, "/v1/accounts/**").hasRole("ADMIN")
.antMatchers(HttpMethod.POST, "/v1/accounts/**").permitAll()
.anyRequest().permitAll()
)
.oauth2Login(
oauth2 -> oauth2
.successHandler(new OAuth2AccountSuccessHandler(jwtTokenizer, authorityUtils, accountService))
);
return httpSecurity.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
//โญ ์ CORS๊ตฌ์ฑ ๊ฐ์ฒด ๋ฑ๋ก
CorsConfiguration configuration = new CorsConfiguration();
//๐ฅ๐ฅ๐ฅ ์๊ฒฉ์ฆ๋ช
(์: ์ฟ ํค, ์ธ์ฆ ํค๋ ๋ฑ)์ ํ์ฉ
configuration.setAllowCredentials(true);
//โญ๋ชจ๋ ์ถ์ฒ(Origin)์ ๋ํด ์คํฌ๋ฆฝํธ ๊ธฐ๋ฐ HTTP ํต์ ํ์ฉ
//๐ฅ๐ฅ๐ฅ configuration.setAllowedOrigins(Arrays.asList("*"));
//โญ ๋จ, setAllowedOrigins(Arrays.asList("*"))์ด ์๋,
//๐ฅ๐ฅ๐ฅ ์ด์ ๋ฒ์ .setAllowedOriginPatterns(Arrays.asList("*")); ๋ ์ฌ์ฉ๊ฐ๋ฅํ์ง๋ง ๊ถ์ฅX
configuration.setAllowedOrigins(Arrays.asList(
"http://localhost:80", // โญ ๋ก์ปฌ HTTP๋ก๋ถํฐ ๋ค์ด์ค๋ ORIGIN ํ์ฉ
"http://localhost:8080", //โญ ๋ก์ปฌ WAS๋ก๋ถํฐ ๋ค์ด์ค๋ ORIGIN ํ์ฉ
"http://localhost:3000", //โญ ๋ก์ปฌ ๋ฆฌ์กํธ์๋ฒ๋ก๋ถํฐ ๋ค์ด์ค๋ ORIGIN ํ์ฉ
"http://seveneleven-stackoverflow-s3.s3-website.ap-northeast-2.amazonaws.com", // โญ S3 ํธ์คํธ ORIGIN ์๋ฒ
"3.36.128.133:8080", "3.36.128.133:80" // โญ EC2 ์๊ธฐ ์์ ๋ฆฌ๋ค์ด๋ ์
?
));
// React์์ ๊ฐ์ ๋ด๊ธฐ ์ํ ๋
ธ์ถ ํค๋๋ฅผ ๋ณ๋๋ก ์ง์ ํด์ผ ํ๋ค. โญ
// (๋ฆฌ์กํธ) ์๋ต ํค๋์ Authorization ํค๋๋ฅผ ๋
ธ์ถํ๋๋ก ์ค์
config.addExposedHeader("Authorization");
config.addExposedHeader("Refresh");
//4๊ฐ์ง HTTP Method์ ๋ํ HTTP ํต์ ํ์ฉ + OPTIONS (CORS ํ๋ฆฌํ๋ผ์ดํธ์ฉ) โญ
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "OPTIONS"));
// CorsConfigurationSource ์ธํฐํ์ด์ค ๊ตฌํ ํด๋์ค์ธ UrlBasedCorsConfigurationSource ํด๋์ค ๊ฐ์ฒด ์์ฑ
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// ๋ชจ๋ URL์ ์๊ธฐ CORS ์ ์ฑ
์ ์ฉ
source.registerCorsConfiguration("/**", configuration);
return source;
}
public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer, HttpSecurity> {
// ... ์ค๋ต
}
}
๐ฅ๐ฅ๐ฅ setAllowCredentials(true)
๊ณผ setAllowedOrigins("*")
๋ ํจ๊ป ์ฌ์ฉํ ์ ์๋ค.
setAllowCredentials(true)
๋ฉ์๋๋ ์์ฒญ์์ ์๊ฒฉ์ฆ๋ช (์ฟ ํค, ์ธ์ฆ ํค๋ ๋ฑ)์ ํ์ฉํ๊ฒ ๋ค๋ ๊ฒ์ ๋ํ๋ด๋ฉฐ,setAllowedOrigins("*")
๋ฉ์๋๋ ๋ชจ๋ ์ถ์ฒ์ ๋ํด ์คํฌ๋ฆฝํธ ๊ธฐ๋ฐ HTTP ํต์ ์ ํ์ฉํ๊ฒ ๋ค๋ ๊ฒ์ ๋ํ๋ธ๋ค. ์ด ๊ฒฝ์ฐ, ๋ชจ๋ ์ถ์ฒ์ ๋ํด ์๊ฒฉ์ฆ๋ช ์ ํ์ฉํ๋ฉด ๋ณด์์์ ์ด์๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์๋ํ๋ฉด ๋ค๋ฅธ ๋๋ฉ์ธ์์๋ ์๊ฒฉ์ฆ๋ช ์ ์์ฒญํ ์ ์๊ฒ ๋๋ฉด์ ๊ฐ์ธ์ ๋ณด ์ ์ถ ๋ฑ์ ์ํ์ด ๋์์ง ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ฐ๋ผ์ ์ด ๋ ๊ฐ์ง ์ต์ ์ ํจ๊ป ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅ๋์ง ์์ต๋๋ค.
(๊ถ์ฅ๋์ง ์์ ๋ฟ๋๋ฌ ์ ์ฉ๋ ๋์ง ์๋๋ค.setAllowedOrigins(Arrays.asList("*"));
์ ์ด์ ๋ฒ์ ์ธ.setAllowedOriginPatterns(Arrays.asList("*"));
๋setAllowCredentials(true)
์ ํจ๊ป ์ฌ์ฉํด๋ ๊ทธ CORS ์ ์ฑ ์ด ์ ์ง๋์ง๋ง ๋ง์ฐฌ๊ฐ์ง์ ์ด์ ๋ก ์ฌ์ฉํ์ง ์๋ ๊ฒ์ด ์ข๋ค.)
๐กAllowedOrigins
๋ Credential
์ด๋ ์ฌ์ฉํ๋ ค๋ฉด ๋ฌด์กฐ๊ฑด "๋ช
์์ ์ผ๋ก ์ถ์ฒ ์์ฑ" ํด์ค์ผ ์ฌ์ฉ๊ฐ๋ฅ
AllowedOriginPattern
์ ์ด์ ๋ฒ์ ์ด๋ผ์ "*
"๋ชจ๋ ์ถ์ฒ ํต์ ํ์ฉ์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅํ์ง๋ง ๊ถ์ฅ์ ํ์ง ์์ : (๋๊ธฐ๋ถ๊ณผ ๋งค๋์ ๋์ ์๊ฒฌ)
- ๋ํ
@CrossOrigin
์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ โโ๋ก allowOrigins์ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด ์๋๊ฐ ์๊ฐ๋๋ค.
(When it is enabled either allowOrigins must be set to one or more specific domain (but not the special value "")
// React์์ ๊ฐ์ ๋ด๊ธฐ ์ํ ๋
ธ์ถ ํค๋๋ฅผ ๋ณ๋๋ก ์ง์ ํด์ผ ํ๋ค. โญโ
// (๋ฆฌ์กํธ) ์๋ต ํค๋์ Authorization ํค๋๋ฅผ ๋
ธ์ถํ๋๋ก ์ค์
config.addExposedHeader("Authorization");
config.addExposedHeader("Refresh");
//4๊ฐ์ง HTTP Method์ ๋ํ HTTP ํต์ ํ์ฉ + OPTIONS (CORS ํ๋ฆฌํ๋ผ์ดํธ์ฉ) โญโก
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "OPTIONS"));
config.addExposedHeader("Authorization");
๋ Authorization ํค ๊ฐ์ผ๋ก ์ ๋ฌ๋๋ ํค๋๋ฅผ React ๋ฑ ํ๋ก ํธ ํ๋ ์์ํฌ์์ ๋ฐ์ ์ ์๋ ํค๋๋ฅผ ๋
ธ์ถ์์ผ์ฃผ์ด์ผ ํ๋ค. (Refresh)๋ ๋ง์ฐฌ๊ฐ์ง.
๐ค์ด ๋ฌธ์ ๋ฅผ ์์ง ๋ชปํด์ Http Response Header์๋ ๋
ธ์ถ๋์ด ์์ง๋ง, ํ๋ก ํธ์์ ๋ฐ์์ง์ง ์๊ธธ๋ ํ๋ก ํธ ๋ฌธ์ ์ธ ๊ฒ์ผ๋ก ์๊ฐํ๊ณ ํ์ฐธ์ ํค๋งธ๋ค. ํ์ง๋ง ์๋ต ํค๋์ ์ ์์ ์ผ๋ก ๋
ธ์ถ๋๋ ๊ฒ ์ด์ธ์๋ ํด๋ผ์ด์ธํธ ๋ธ๋ผ์ฐ์ ๋จ์์ ๋
ธ์ถ๋๋ ํค๋๋ ๋ณ๋์ด๊ธฐ ๋๋ฌธ์
๋ฐ๋์ ๋ณ๋์ ์ค์ ์ ํด์ค์ผ ํ๋ค.
๋ธ๋ผ์ฐ์ ๋ Cross-Origin ์์ฒญ ์ ๋ณด์์ ์ํด "์ฌ์ง์ด ๋ ธ์ถ๋์ด ์๋ ํค๋๋ ์๋์ผ๋ก ์ ๊ทผํ ์ ์๋๋ก" ๋ณดํธํ๋ ๊ฒฝํฅ์ด ์์ต๋๋ค. ์ด๊ฒ์ด ๋ฐ๋ก ๊ธฐ๋ณธ์ ์ธ ๋ณด์ ์ ์ฑ ์ธ ๋์ผ ์ถ์ฒ ์ ์ฑ (Same-Origin Policy)์ ๋๋ค. ๋ฐ๋ผ์ Access-Control-Expose-Headers ํค๋๋ฅผ ์ฌ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ์๊ฒ ํ์ฉํ ์ถ๊ฐ ํค๋๋ฅผ ์๋ ค์ฃผ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
์ด๋ ๊ฒ ์ถ๊ฐ ํค๋๋ฅผ ๋ช ์์ ์ผ๋ก ์๋ ค์ค์ผ๋ก์จ, ํ๋ก ํธ์๋์์ ํด๋น ํค๋์ ์ ๊ทผํ ์ ์๊ฒ ํด์ค๋๋ค. ๋ฐ๋ผ์ "์๋ต response ํค๋์ ์ ์์ ์ผ๋ก ๋ ธ์ถ๋์ด ์๋๋ฐ ๋ณ๋์ ์์ ์ด ํ์ํ ์ด์ "๋ ๊ธฐ๋ณธ์ ์ธ ๋ธ๋ผ์ฐ์ ๋ณด์ ์ ์ฑ ์ ์ฐํํ๊ณ ํค๋์ ์ ๊ทผํ ์ ์๋๋ก ํด์ฃผ๊ธฐ ์ํจ์ ๋๋ค.
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "OPTIONS"));
์์๋ ํ๋ฆฌํ๋ผ์ดํธ ์์ฒญ๊น์ง ํ์ฉํ๊ธฐ ์ํด OPTIONS
๋ฅผ ๋ถ์ฌ์ค๋ค.
(์ฐธ๊ณ ) CorsConfigurationSource ๋น์ Spring Security ์ค์ ์ค์์ CORS(Cross-Origin Resource Sharing) ์ ์ฑ ์ ์ ์ฉํ๋ ๋จ๊ณ์์ ์ฌ์ฉ๋๋ค.
http.cors()
// โญCorsConfigurationSource
๋น์ผ๋ก ์ง์ ํด๋์๋ CORS ์ค์ ์ ์ฉ๋๋ ์์ .
์ต์์_๋๋ ํ ๋ฆฌ/workflow/server.yml
์ ๋ฐ๋ฅธ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๊ฒ ๋๋ค.
- ๋ค์์
EC2 SSH ํ๋กํ ์ฝ ํต์
์ ๋ชฉ์ ์ผ๋กํ๋server.yml
ํ์ผ์ด๋ค.
(์ด ๊นํ ์ก์ ์ํฌํ๋ก์ฐ๋ ์ฃผ์ด์ง ์ ์ฅ์์ ์ฝ๋ ๋ณ๊ฒฝ ์ฌํญ์ ์ฒ๋ฆฌํ๋ CI/CD ํ์ดํ๋ผ์ธ์ด๋ฉฐ, ์ด ์ํฌํ๋ก์ฐ๋ GitHub ์ ์ฅ์์ main ๋ธ๋์น์ ํธ์๋์์ ๋ ์คํ๋๋ฉฐ, ๋ค์๊ณผ ๊ฐ์ ๋จ๊ณ๋ฅผ ๊ฑฐ์ณ ๋น๋ ๋ฐ ๋ฐฐํฌ๋ฅผ ์๋ํํ๋ค.)
server.yml
ํ์ผ ์์น
# โญ ์ด workflow์ ์ด๋ฆ์ Server CI/CD์ด๋ค.
name: Server CI/CD
# โญ main ๋ธ๋์น์ push ๋ ๋ ์ด ์์
์ด ์ํ๋๋ค. (cronetab์ผ๋ก ๋ฐฐ์น ์ค์ ๋ ๊ฐ๋ฅํ๋ค.)
on:
push:
branches: [ main ]
# โญ `ubuntu-latest`, ์ต์ ๋ฒ์ ์ ์ฐ๋ถํฌ ํ๊ฒฝ์์ ์ํํ 'build'๋ผ๋ ์์
์ ์ ์
jobs:
build:
runs-on: ubuntu-latest
# โญ ํ๊ฒฝ๋ณ์๋ฅผ ํตํด ๋ณด์์ ๋์์ด ๋๋ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์ ์ฅ, ๋์ปค -> EC2 ubuntu๋ก ๋๊ฒจ์ค.
env: # โญ ${}๋ GitHub Secret์์ ๋ณ๋๋ก ๋ณ์ ์ง์
G_CLIENT_ID: ${{secrets.G_CLIENT_ID}} # OAuth ๊ด๋ จ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ์์ด๋ ํค
G_CLIENT_SECRET: ${{secrets.G_CLIENT_SECRET}} # OAuth ๋น๋ฐํค
JWT_SECRET_KEY: ${{secrets.JWT_SECRET_KEY}} # JWT ์ํ๋ฆฌํฐ์ ์ฌ์ฉํ๋ ํค
working-directory: ./be/stackoverflow-be # โญ ๋น๋ํ์ผ์ด ์๋ ์์น๋ฅผ ์ง์
#โญ ์ค์ ์์
์ ๋จ๊ณ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ steps
steps:
# โญ 'uses' ๋ชจ๋ํ๋ ํน์ ์ก์
์ ์ฌ์ฉ
- uses: actions/checkout@v2 # ์ด ๋ชจ๋์ ์ก์
์ ์ฌ์ฉํ์ฌ ์ ์ฅ์ ์ฝ๋๋ฅผ ์ฒดํฌ์์ ํจ.
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'zulu'
# โญ gradle๋ก ๋น๋ํ๊ธฐ ์ํ ์คํ๊ถํ ๋ถ์ฌ
- name: Grant execute permission for gradlew
run: chmod +x gradlew
working-directory: ${{ env.working-directory }} # ๋น๋๊ฐ ๊ฐ๋ฅํ ๋๋ ํ ๋ฆฌ์์ ํด์ผํจ!
# โญ Gradle์ ํตํด ์ค์ ๋น๋ ์ํ
- name: Build with Gradle
run: ./gradlew build
working-directory: ${{ env.working-directory }}
# โญ Docker ์ด๋ฏธ์ง ๋น๋ ๋ฐ ํธ์
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} # id์ ํจ์ค์๋๋ฅผ ์ด์ฉํ ๋ก๊ทธ์ธ
docker build -t {๋์ปคํ๋ธ์ ์ฅ์๋ช
} .
docker tag {๋์ปคํ๋ธ์ ์ฅ์๋ช
} {๋์ปค์ฌ์ฉ์}/{๋์ปคํ๋ธ์ ์ฅ์๋ช
}:${GITHUB_SHA::7}
docker push {๋์ปค์ฌ์ฉ์}/{๋์ปคํ๋ธ์ ์ฅ์๋ช
}:${GITHUB_SHA::7}
working-directory: ${{ env.working-directory }}
# โญ AWS ์ธ์ฆ ์ ๋ณด ๊ตฌ์ฑ
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1 # ํด๋น ๋ชจ๋์ aws ์ธ์ฆ์ ๋ณด๋ฅผ ๊ตฌ์ฑํ๋ค.
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} # ์ก์ธ์ค ํค
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # ๋น๋ฐ ์ก์ธ์ค ํค
aws-region: ap-northeast-2
# โญ SSH ์ฐ๊ฒฐ ๋ฐ ์๋ฒ ๋ฐฐํฌ (๋ง์ฝ ์ธ์
๋งค๋์ ๋ฅผ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด ์ด ๋ถ๋ถ์ ์ค์ ์ ์ธ์
๋งค๋์ ๋ก ๋ณ๊ฒฝ)
- name: SSH Connection and Deploy to Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.AWS_SSH_HOST }} # โญ ๋ฐฐํฌ ์๋ฒ์ ๋ํ public ipv4 ์ฃผ์ ๋๋ ๋๋ฉ์ธ๋ค์
username: ${{ secrets.AWS_SSH_USERNAME }} # โญ ubuntu (EC2 ํ๊ฒฝ SSH_USERNAME)
key: ${{ secrets.AWS_SSH_KEY }} # โญ SSH ํค (pemํค -----BEGIN~END~KEY--- ๋ชจ๋ ํฌํจ)
# port: ${{ secrets.AWS_SSH_PORT }} # SSH ํฌํธ but ๋ํดํธ ์ค์ ์ผ๋ก ๊ตณ์ด ์ค์ ํ์ง ์์๋๋จ.
envs: GITHUB_SHA
script: |
sudo docker rm -f server
sudo docker pull {๋์ปค์ฌ์ฉ์๋ช
/๋์ปคํ๋ธ์ ์ฅ์๋ช
}:${GITHUB_SHA::7}
sudo docker tag {๋์ปค์ฌ์ฉ์๋ช
/๋ ํฌ์งํ ๋ฆฌ}:${GITHUB_SHA::7} ๋ ํฌ์งํ ๋ฆฌ
sudo docker run -d --name server -p 8080:8080 ๋ ํฌ์งํ ๋ฆฌ # โญ ํฌํธํฌ์๋ฉ์ http:WAS๋ก ํ๋ ค๋ฉด 80:8080(?)
์ฌ์ฉ์๋ช
/๋ ํฌ์งํ ๋ฆฌ๋ช
์ ๋ ํฌ์งํ ๋ฆฌ๊ฐ ์์ฑ๋๋ค.# (1) base-image
FROM openjdk:11
# โญ 'ARG' ์์ฝ์ด๋ฅผ ํตํด ์ธ์๋ก ์ ๋ฌ ๋ฐ์์ผ ํ๋ค.
ARG MYSQL_ID \
MYSQL_PASSWORD \
RDS_URL
# โญ 'ENV' ์์ฝ์ด๋ฅผ ํตํด ์ ๋ฌ๋ฐ์ ๊ฐ์ ์ค์ ๊ฐ๊ณผ ๋งค์นญ์์ผ์ผ ํ๋ค.
ENV MYSQL_ID=${MYSQL_ID} \
MYSQL_PASSWORD=${MYSQL_PASSWORD} \
RDS_URL=${RDS_URL}
# (2) COPY์์ ์ฌ์ฉ๋ ๊ฒฝ๋ก ๋ณ์
ARG JAR_FILE=build/libs/*-SNAPSHOT.jar
# (3) jar ๋น๋ ํ์ผ์ ๋์ปค ์ปจํ
์ด๋๋ก ๋ณต์ฌ
COPY ${JAR_FILE} app.jar
# (4) jar ํ์ผ ์คํ ์ 'SPRING_PROFILES_ACTIVE' ํ๊ฒฝ ๋ณ์ ์ง์ ํ์ฌ ์คํ
# โญ'dev'๋ ์ํ๋ ํ๋กํ์ผ๋ก ๋ณ๊ฒฝํ๋ฉด ๋๋ค.
ENTRYPOINT ["java", "-Dspring.profiles.active=server", "-jar", "/app.jar"]
application.yml
spring:
profiles:
active: server
application-server.yml
spring:
datasource:
url: ${RDS_URL}
username: ${MYSQL_ID}
password: ${MYSQL_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
database: mysql
show-sql: true
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
# security:
# oauth2:
# client:
# registration:
# google:
# client-id: ${G_CLIENT_ID}
# client-secret: ${G_CLIENT_SECRET}
# redirect-uri: http://ec2-3-36-128-133.ap-northeast-2.compute.amazonaws.com/login/oauth2/code/google
# scope:
# - email
# - profile
#jwt:
# key:
# secret: ${JWT_SECRET_KEY} # ??? ??? ??? ?? ???? ??
# access-token-expiration-minutes: 30
# refresh-token-expiration-minutes: 420
logging:
level:
org:
hibernate:
type:
descriptor: trace
# swagger
springdoc:
swagger-ui:
path: /swagger
operationsSorter: alpha
tags-sorter: alpha
disable-swagger-default-url: true
display-request-duration: true
api-docs:
path: /api-docs
show-actuator: true
default-consumes-media-type: application/json
default-produces-media-type: application/json
paths-to-match:
- /v1/**
#mail:
# admin:
# address: ${ADMIN_EMAIL}
server:
port: 8080
application-local.yml
spring:
config:
activate:
on-profile: local
h2:
console:
enabled: true
path: /h2
jpa:
database: h2
show-sql: true
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
# security:
# oauth2:
# client:
# registration:
# google:
# client-id: ${G_CLIENT_ID}
# client-secret: ${G_CLIENT_SECRET}
# redirect-uri: http://ec2-3-36-128-133.ap-northeast-2.compute.amazonaws.com/login/oauth2/code/google
# scope:
# - email
# - profile
#jwt:
# key:
# secret: ${JWT_SECRET_KEY} # ??? ??? ??? ?? ???? ??
# access-token-expiration-minutes: 30
# refresh-token-expiration-minutes: 420
logging:
level:
org:
hibernate:
type:
descriptor: trace
# swagger
springdoc:
swagger-ui:
path: /swagger
operationsSorter: alpha
tags-sorter: alpha
disable-swagger-default-url: true
display-request-duration: true
api-docs:
path: /api-docs
show-actuator: true
default-consumes-media-type: application/json
default-produces-media-type: application/json
paths-to-match:
- /v1/**
#mail:
# admin:
# address: ${ADMIN_EMAIL}
server:
port: 8888
์ธ์
๋งค๋์
์ค์ ๊ณผ SSH ํ๋กํ ์ฝ
์ ํตํ ํต์ ๊ณผ์ ์ด ๋ค๋ฅด๋ค.๊นํ์ก์
-์ธ์
๋งค๋์
์ฐ๋์ ์ํ ๊ถํ ์ ์ฑ
์ ์๊ฐํ๋ค.
- AmazonSSMRoleForInstanceQuickSetup ( EC2 ์ธ์คํด์ค์ ๋ํ Systems Manager (SSM) ์๋น์ค์ ์ก์ธ์ค๋ฅผ ํ์ฉ)
AWS-QuickSetup-HostMgmRole-ap-northeast-2-3483b (ํน์ ์ง์ญ(ap-northeast-2)์์ ํธ์คํธ ๊ด๋ฆฌ ์์ ์ ์ํ ์ญํ )
AWS-QuickSetup-StackSet-Local-AdministrationRole ( AWS CloudFormation ์คํ ์งํฉ (Stack Set) ๊ด๋ฆฌ ์์ )
AWS-QuickSetup-StackSet-Local-ExecutionRole ( AWS Systems Manager ์๋น์ค๊ฐ ๋ค๋ฅธ AWS ๋ฆฌ์์ค์ ์ํธ ์์ฉํ ์ ์๋ ๊ถํ์ ๋ถ์ฌ )
AWSServiceRoleForAmazonSSM ( Amazon RDS ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋น์ค์ ๊ด๋ จ๋ ์์ ์ ์ํํ๊ธฐ ์ํ ๊ถํ์ ์ ๊ณต)
AWSServiceRoleForRDS (Amazon RDS ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋น์ค์ ๊ด๋ จ๋ ์์ ์ ์ํํ๊ธฐ ์ํ ๊ถํ์ ์ ๊ณต)
rds-monitoring-role (Amazon RDS ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ์คํด์ค์ ๋ชจ๋ํฐ๋ง์ ์ํด ํ์ํ ๊ถํ์ ๋ถ์ฌ)
EC2์ ๋ณด์ ๊ทธ๋ฃนโญ์ ์ธ์คํด์ค์ ๋คํธ์ํฌ ํธ๋ํฝ์ ์ ์ดํ๊ธฐ ์ํ ๊ฐ์ ๋ฐฉํ๋ฒฝ ์ญํ ์ ํ๋ค. ์ด ๋ณด์ ๊ทธ๋ฃน์ ์ธ๋ฐ์ด๋(๋ค์ด์ค๋ ํธ๋ํฝ) ๋ฐ ์์๋ฐ์ด๋(๋๊ฐ๋ ํธ๋ํฝ) ๊ท์น์ ์ค์ ํ์ฌ ์ด๋ค ์ ํ์ ํธ๋ํฝ์ด ํ์ฉ๋๊ฑฐ๋ ์ฐจ๋จ๋ ์ง๋ฅผ ๊ฒฐ์ ํ๋ค.
์ธ๋ฐ์ด๋ ๊ท์นโก์ EC2 ์ธ์คํด์ค๋ก ๋ค์ด์ค๋ ํธ๋ํฝ์ ์ ์ดํ๋ค. ๊ฐ ๊ท์น์ ํน์ ํฌํธ ๋ฒ์์ IP ์ฃผ์ ๋ฒ์๋ฅผ ์ง์ ํ์ฌ ์ค์ ๋ ์ ์๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์๋ํ๋ค:
- ํฌํธ ๋ฒ์: ํธ๋ํฝ์ ์ด๋ ํฌํธ๋ก ๋ณด๋ผ ๊ฒ์ธ์ง๋ฅผ ์ง์ ํ๋ค. ์๋ฅผ ๋ค์ด, ์น ์๋ฒ์ฉ HTTP ํธ๋ํฝ์ ๋ณดํต 80 ํฌํธ๋ก ์ค์ ๋๋ค.
- IP ์ฃผ์ ๋ฒ์: ์ด๋ค IP ์ฃผ์๋ IP ๋ฒ์๋ก๋ถํฐ ํธ๋ํฝ์ ํ์ฉํ ์ง๋ฅผ ๊ฒฐ์ ํ๋ค. ํน์ IP ์ฃผ์, ์๋ธ๋ท, ๋๋ ๋ชจ๋ IP ์ฃผ์ (0.0.0.0/0)๋ก๋ถํฐ ํธ๋ํฝ์ ํ์ฉํ ์ ์๋ค.
๐ก ์๋ฅผ ๋ค์ด, ์น ์๋ฒ๋ฅผ ํธ์คํ ํ๋ EC2 ์ธ์คํด์ค์ ๊ฒฝ์ฐ, 80 ํฌํธ๋ก๋ถํฐ ๋ชจ๋ IP ์ฃผ์๋ก๋ถํฐ์ ์ธ๋ฐ์ด๋ HTTP ํธ๋ํฝ์ ํ์ฉํ๋ ๊ท์น์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ์น ์๋ฒ์ ๋ํ ์ธ๋ถ์ ์ ๊ทผ์ ํ์ฉํ ์ ์๊ฒ ๋๋ค.
๋ณด์ ๊ทธ๋ฃน์ ์ฌ๋ฌ ๊ท์น์ ๊ฐ์ง ์ ์์ผ๋ฉฐ, ๋ณด์ ๊ทธ๋ฃน ๊ฐ์ ์กฐํฉํ์ฌ ๋ค์ํ ๋คํธ์ํฌ ๊ตฌ์ฑ์ ์ค์ ํ ์ ์๋ค. ์ด๋ฅผ ํตํด ํ์ํ ํธ๋ํฝ๋ง ํ์ฉํ๊ณ ๋๋จธ์ง๋ ์ฐจ๋จํ์ฌ ์ธ์คํด์ค์ ๋ณด์์ ๊ฐํํ ์ ์๋ค.
ํ ํ๋ก์ ํธ์์ ์ค์ ํ ๋ณด์ ๊ทธ๋ฃน์ ์ธ๋ฐ์ด๋ ๊ท์น์ ๋ค์๊ณผ ๊ฐ๋ค.
(EC2 > ์ธ์คํด์ค > (์ธ์คํด์ค ์ธ๋ถ ์ ๋ณด)
)
3306
(MySQL),80
(HTTP),22
(SSH ํ๋กํ ์ฝ),8080
(WAS),3000
(React) ๋ฑ์ด ์ฃผ์ ๊ด๋ฆฌ ๋์์ด๋ค. ์ธ๋ฐ์ด๋๋ก ์ ์ฒด ํฌํธ๋ฅผ ํ์ฉํ๋ ๊ฒ์ ์ฌ์ค, ์ถ์ฒ๋์ง ์๋๋ค.
server.yml
ํ์ผ์ ๋ด์์ฃผ๋ ๊ฒ์ ์ด๋ค ์ฐจ์ด๊ฐ ์กด์ฌํ ๊น?๊นํ๋ธ๋ ํฌ์งํ ๋ฆฌ - Setting - secret and variables - Actions
์ ์ค์ ๋ ๋ณ์๋คMySQL์ ์ค์นํ๋ ๊ฒ์ ๋ณดํต AWS ์๋น์ค ์ค RDS๊ฐ ๋ ํ๋ฅ ์ด ๋์ง๋ง, ์๋ฒ ๋น์ฉ์ด ๋น์ธ๋ค๋ ๋จ์ ๋๋ฌธ์ ์ด๊ธฐ ์ธํ
์ EC2์ ํด๋์๋ค๊ฐ ์ดํ์ RDS๋ก ์ฎ๊ฒจ๊ฐ๋ ๋ฐฉ์์ ์ฑํํ ์ ์๋ค.
๋ฐ๋ผ์ ํ๋ก์ ํธ ์ด๊ธฐ, EC2์ MySQL ์๋ฒ๋ฅผ ๋๊ธฐ๋ก ํฉ์ํ์๋ค.
๋์ปค๋ฅผ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์
๊ณผ ๊ทธ์ ํ์ํ ๋ชจ๋ ์ข
์์ฑ์ ์ปจํ
์ด๋๋ก ํจํค์งํ ์ ์๋ค. ์ด๋ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ฒฉ๋ฆฌ๋ ํ๊ฒฝ์์ ์คํํ๊ณ ์ธ์คํด์ค ๊ฐ์ ์ผ๊ด๋ ๋ฐฐํฌ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ค.
๋ํ ํธ์คํธ ํ๊ฒฝ์ ๋
๋ฆฝ์ ์ธ ๋์ปค๋ ์ด์์ฑ๊ณผ ํ์ฅ์ฑ์ ์ฅ์ ์ ๊ฐ์ง๊ณ ์๋ค.
Q. ์ธ๋ฐ์ด๋ ๊ท์น์ผ๋ก EC2๋ก ํ์ฉ๋๋ ํฌํธ๋ฅผ ์ค์ ํ์ง ์์๋?
UBUNTU (EC2)ํ๊ฒฝ์ ๋ณ๋๋ก ํ์ฉ ํฌํธ๋ฅผ ์ง์ ํ ์ด์ ๋ ๋ฌด์์ธ๊ฐ?
# UFW ์ค์น ๋ฐ ํ์ฑํ:
sudo apt update
sudo apt install ufw
sudo ufw enable
# ํน์ ํฌํธ ํ์ฉ : sudo ufw allow <PORT_NUMBER>
sudo ufw allow 80
# ๊ท์น ํ์ธ, ํฌํธ๊ฐ ์ ๋๋ก ์ด๋ ธ๋์ง ํ์ธ:
sudo ufw status
# ๊ท์น ์ ์ฉ:
sudo ufw reload
iptables ๊ฐ๋ ๋ฐ ๋ช ๋ น์ด (์คํ๊ถํ ๋ถ์ฌ๋ฅผ ์ํด ๋๋ถ๋ถ ๋ช ๋ น์ด์
sudo
๋ถ์ฌ์ผํจ.)
[AWS] Ubuntu22.04 ํ๊ฒฝ EC2์์ ์คํ๋ง๋ถํธ ๋ฐ์นญํ๊ธฐ
AWS ์๋ฒ ํฌํธ Openํ๊ธฐ
application.yml
์ค์ (-server ๋ฑ์ ํ๋กํ์ผ์ ์ ์ฉํ ์๋ ์๋ค.)
- ํ๋กํ์ผ์ ์ ์ฉํ๊ฒ ๋๋ฉด
Dockerfile
์ ์คํฌ๋ฆฝํธ๋ ๊นํ๋ธ์ก์ ์ํฌํ๋ก์ฐ yml ํ์ผ์ ์คํฌ๋ฆฝํธ์์ ๋น๋ ๋ช ๋ น์ด๋ ์ด์ ๋ถํฉํ๊ฒ๋ ์์ ํด์ผ ํ๋ค.
spring:
# h2:
# enabled: true
# path: /h2
# datasource:
# url: jdbc:h2:mem:stackoverflow
datasource: #datasource_url๋ ํ๊ฒฝ๋ณ์๋ก ๊ด๋ฆฌํ์!
url: jdbc:mysql://seveneleven.c1vvhrsbxo9y.ap-northeast-2.rds.amazonaws.com:3306/stackoverflow?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
username: ${MYSQL_ID} # ์ ์ ๋ค์
password: ${MYSQL_PASSWORD} # ๋น๋ฐ๋ฒํธ
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
database: mysql #h2
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
security:
oauth2:
client:
registration:
google:
client-id: ${G_CLIENT_ID}
client-secret: ${G_CLIENT_SECRET}
redirect-uri: http://ec2-3-36-128-133.ap-northeast-2.compute.amazonaws.com/login/oauth2/code/google
scope:
- email
- profile
jwt:
key:
secret: ${JWT_SECRET_KEY} # ๋ฏผ๊ฐํ ์ ๋ณด๋ ์์คํ
ํ๊ฒฝ ๋ณ์์์ ๋ก๋
access-token-expiration-minutes: 30
refresh-token-expiration-minutes: 420
logging:
level:
org:
hibernate:
type:
descriptor: trace
# swagger
springdoc:
swagger-ui:
path: /swagger
operationsSorter: alpha
tags-sorter: alpha
disable-swagger-default-url: true
display-request-duration: true
api-docs:
path: /api-docs
show-actuator: true
default-consumes-media-type: application/json
default-produces-media-type: application/json
paths-to-match:
- /v1/**
mail:
admin:
address: ${ADMIN_EMAIL}