[PreProject] ๐Ÿšฉ EC2 ํ™˜๊ฒฝ์„ธํŒ…, CORS, UBUNTU ํฌํŠธํ—ˆ์šฉ

NtoZยท2023๋…„ 8์›” 18์ผ
1

PreProject

๋ชฉ๋ก ๋ณด๊ธฐ
7/12
post-thumbnail

EC2 ํ™˜๊ฒฝ์„ค์ •



๊ฐœ์š”

  • ๋ณธ ๋ฌธ์„œ์—์„œ๋Š” AWS์˜ ์›น ์„œ๋ฒ„ ๊ตฌ๋™์šฉ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค์ธ S3์™€ WAS ๊ตฌ๋™์šฉ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค์ธ EC2์˜ ์„œ๋ฒ„ ์—ฐ๋™ ์ค‘ ๋ฐœ์ƒํ–ˆ๋˜ ๋ฌธ์ œ ์ƒํ™ฉ์— ๋Œ€ํ•ด ๋ช…ํ™•ํžˆ ํŒŒ์•…ํ•˜๊ณ  ํ™˜๊ฒฝ ์„ธํŒ…์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜๊ณ ์ž ํ•œ๋‹ค.
    ๊ธฐ๋ณธ์ ์œผ๋กœ CI/CD์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค.

  • ๋ณธ ๋ฌธ์„œ๋Š” GitHub Action์„ ํ†ตํ•œ CI/CD๋ฅผ ๋‹ค๋ฃจ๋ฉฐ,

  • ๋˜ํ•œ, ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์ด์šฉํ•˜์—ฌ S3 ํ˜ธ์ŠคํŠธ๋กœ๋ถ€ํ„ฐ ๋“ค์–ด์˜จ ์š”์ฒญ์„ EC2๊ฐ€ ๋ฐ›์•„๋“ค์ผ ์ˆ˜ ์žˆ๋„๋กํ•˜๋Š” CORS ์„ค์ •๋ฒ•์„ ๋‹ค๋ค„๋ณธ๋‹ค.



SpringSecurity์˜ 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์€ ์ด์ „ ๋ฒ„์ „์ด๋ผ์„œ "*"๋ชจ๋“  ์ถœ์ฒ˜ ํ†ต์‹  ํ—ˆ์šฉ์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ๊ถŒ์žฅ์€ ํ•˜์ง€ ์•Š์Œ : (๋™๊ธฐ๋ถ„๊ณผ ๋งค๋‹ˆ์ €๋‹˜์˜ ์˜๊ฒฌ)

// 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 ์„ค์ • ์ ์šฉ๋˜๋Š” ์‹œ์ .
  • [์ฐธ๊ณ ] MDN | CORS ์ •์ฑ…



GitHub Action์„ ์ด์šฉํ•œ CI/CD

  • GitHub Action์—์„œ ๋ฐฐํฌ์ž๋™ํ™”๋ฅผ ์œ„ํ•œ ์ตœ์ƒ์œ„_๋””๋ ‰ํ† ๋ฆฌ/workflow/server.yml์— ๋”ฐ๋ฅธ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค.
    • ๋‹ค์Œ์€ EC2 SSH ํ”„๋กœํ† ์ฝœ ํ†ต์‹ ์„ ๋ชฉ์ ์œผ๋กœํ•˜๋Š” server.yml ํŒŒ์ผ์ด๋‹ค.
      (์ด ๊นƒํ—™ ์•ก์…˜ ์›Œํฌํ”Œ๋กœ์šฐ๋Š” ์ฃผ์–ด์ง„ ์ €์žฅ์†Œ์˜ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” CI/CD ํŒŒ์ดํ”„๋ผ์ธ์ด๋ฉฐ, ์ด ์›Œํฌํ”Œ๋กœ์šฐ๋Š” GitHub ์ €์žฅ์†Œ์˜ main ๋ธŒ๋žœ์น˜์— ํ‘ธ์‹œ๋˜์—ˆ์„ ๋•Œ ์‹คํ–‰๋˜๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์ณ ๋นŒ๋“œ ๋ฐ ๋ฐฐํฌ๋ฅผ ์ž๋™ํ™”ํ•œ๋‹ค.)
      server.yml ํŒŒ์ผ ์œ„์น˜

/.github/workflows/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(?)
  • ์œ„์˜ ๋กœ์ง์— ๋”ฐ๋ผ ์ž๋™์œผ๋กœ ๋„์ปคํ—ˆ๋ธŒ์— ์‚ฌ์šฉ์ž๋ช…/๋ ˆํฌ์ง€ํ† ๋ฆฌ๋ช…์˜ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

- workflow ์ฃผ์š” ๋กœ์ง

- workflow ์ฃผ์š” ๊ตฌ์„ฑ ์š”์†Œ

Dockerfile

Dockerfile ์œ„์น˜

  • ์‹ค์ œ be ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ ์•ˆ์— ์กด์žฌํ•˜๋Š” Dockerfile ์œ„์น˜

๋„์ปคํŒŒ์ผ ์˜ˆ์‹œ (ํ”„๋กœํŒŒ์ผ ์ ์šฉ๋ฒ„์ „)

# (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 ์˜ˆ์‹œ

  • 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


AWS EC2 ๊ถŒํ•œ(์ •์ฑ…) ์„ค์ •

์ฐธ๊ณ ) AWS IAM ๊ณ„์ • ๋“ฑ ๊ธฐ๋ณธ ์„ค์ •

AWS EC2 ๊ถŒํ•œ ์„ค์ •

  • AWS EC2 ๊ถŒํ•œ ์„ค์ •์— ๊ด€ํ•ด์„œ ์„ธ์…˜ ๋งค๋‹ˆ์ € ์„ค์ •๊ณผ SSH ํ”„๋กœํ† ์ฝœ์„ ํ†ตํ•œ ํ†ต์‹  ๊ณผ์ •์ด ๋‹ค๋ฅด๋‹ค.
    ์šฐ๋ฆฌ ์กฐ๋Š” EC2 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 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ์Šคํ„ด์Šค์˜ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์œ„ํ•ด ํ•„์š”ํ•œ ๊ถŒํ•œ์„ ๋ถ€์—ฌ)

AWS EC2 ๋ณด์•ˆ ๊ทธ๋ฃน (์ธ๋ฐ”์šด๋“œ ๊ทœ์น™)

์ธ๋ฐ”์šด๋“œ ๊ทœ์น™์˜ ์„ค์ •

  • 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) ๋“ฑ์ด ์ฃผ์š” ๊ด€๋ฆฌ ๋Œ€์ƒ์ด๋‹ค. ์ธ๋ฐ”์šด๋“œ๋กœ ์ „์ฒด ํฌํŠธ๋ฅผ ํ—ˆ์šฉํ•˜๋Š” ๊ฒƒ์€ ์‚ฌ์‹ค, ์ถ”์ฒœ๋˜์ง€ ์•Š๋Š”๋‹ค.


EC2 ํ™˜๊ฒฝ์„ธํŒ…

EC2 ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •

  • โ‘  G_CLIENT_ID, JWT_SECRET_KEY์™€ ๊ฐ™์€ ๋ณ€์ˆ˜๋“ค์„ ์šฐ๋ถ„ํˆฌ(EC2) ํ™˜๊ฒฝ์—์„œ ์ง์ ‘ ์„ธํŒ…ํ•˜๋Š” ๊ฒƒ๊ณผ โ‘ก๊นƒํ—ˆ๋ธŒ์•ก์…˜ ์›Œํฌํ”Œ๋กœ์šฐ์˜ server.yml ํŒŒ์ผ์— ๋‹ด์•„์ฃผ๋Š” ๊ฒƒ์€ ์–ด๋–ค ์ฐจ์ด๊ฐ€ ์กด์žฌํ• ๊นŒ?

    ํ›„์ž์˜ ๊ฒฝ์šฐ ๊นƒํ—ˆ๋ธŒ์•ก์…˜์˜ secret variables๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž๊ฐ€ ์ง€์ •ํ•œ ๋ณ€์ˆ˜๋ฅผ EC2 ํ™˜๊ฒฝ์— ๋„˜๊ฒจ ์ฃผ๋Š” ๊ฒƒ์ด๋ฏ€๋กœ, ๊นƒํ—ˆ๋ธŒ ์•ก์…˜ ํ•˜๋‚˜์—์„œ ๋ณด์•ˆ ์ •๋ณด๋ฅผ ์ผ์›ํ™”ํ•˜์—ฌ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.
    ๋”ฐ๋ผ์„œ ํ˜„ ํ”„๋กœ์ ํŠธ์˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ธํŒ…์€ ํ›„์ž๋ฅผ ๋”ฐ๋ฅธ๋‹ค.
    ๊นƒํ—ˆ๋ธŒ๋ ˆํฌ์ง€ํ† ๋ฆฌ - Setting - secret and variables - Actions์— ์„ค์ •๋œ ๋ณ€์ˆ˜๋“ค

MySQL, ๋„์ปค ์„ค์น˜ ๋“ฑ

  • MySQL์„ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ์€ ๋ณดํ†ต AWS ์„œ๋น„์Šค ์ค‘ RDS๊ฐ€ ๋  ํ™•๋ฅ ์ด ๋†’์ง€๋งŒ, ์„œ๋ฒ„ ๋น„์šฉ์ด ๋น„์‹ธ๋‹ค๋Š” ๋‹จ์  ๋•Œ๋ฌธ์— ์ดˆ๊ธฐ ์„ธํŒ…์„ EC2์— ํ•ด๋‘์—ˆ๋‹ค๊ฐ€ ์ดํ›„์— RDS๋กœ ์˜ฎ๊ฒจ๊ฐ€๋Š” ๋ฐฉ์‹์„ ์ฑ„ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.
    ๋”ฐ๋ผ์„œ ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ, EC2์— MySQL ์„œ๋ฒ„๋ฅผ ๋‘๊ธฐ๋กœ ํ•ฉ์˜ํ•˜์˜€๋‹ค.

  • ๋„์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๊ทธ์— ํ•„์š”ํ•œ ๋ชจ๋“  ์ข…์†์„ฑ์„ ์ปจํ…Œ์ด๋„ˆ๋กœ ํŒจํ‚ค์ง•ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฒฉ๋ฆฌ๋œ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•˜๊ณ  ์ธ์Šคํ„ด์Šค ๊ฐ„์— ์ผ๊ด€๋œ ๋ฐฐํฌ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•œ๋‹ค.
    ๋˜ํ•œ ํ˜ธ์ŠคํŠธ ํ™˜๊ฒฝ์— ๋…๋ฆฝ์ ์ธ ๋„์ปค๋Š” ์ด์‹์„ฑ๊ณผ ํ™•์žฅ์„ฑ์— ์žฅ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.


EC2 ํฌํŠธ ๊ด€๋ จ ์„ธํŒ…

Q. ์ธ๋ฐ”์šด๋“œ ๊ทœ์น™์œผ๋กœ EC2๋กœ ํ—ˆ์šฉ๋˜๋Š” ํฌํŠธ๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์•˜๋‚˜?
UBUNTU (EC2)ํ™˜๊ฒฝ์— ๋ณ„๋„๋กœ ํ—ˆ์šฉ ํฌํŠธ๋ฅผ ์ง€์ •ํ•  ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€?

  • A. ๋ณด์•ˆ ๊ทธ๋ฃน์˜ ์ธ๋ฐ”์šด๋“œ ๊ทœ์น™์œผ๋กœ ํ—ˆ์šฉ ํฌํŠธ๋ฅผ ์ง€์ •ํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  EC2 ์ธ์Šคํ„ด์Šค ๋‚ด์—์„œ ๋ณ„๋„์˜ ํฌํŠธ ๋ฒˆํ˜ธ ๊ณผ์ •์„ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ์ด์œ ๋Š” ์ฃผ๋กœ ๋‘ ๊ฐ€์ง€ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

    โ‘  ๋ฐฉํ™”๋ฒฝ ๋ฐ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์„ค์ •: ๋ณด์•ˆ ๊ทธ๋ฃน์€ AWS ๋„คํŠธ์›Œํฌ ์ˆ˜์ค€์—์„œ ์ž‘๋™ํ•˜๋Š” ๊ฐ€์ƒ ๋ฐฉํ™”๋ฒฝ์ด๋ฉฐ, EC2 ์ธ์Šคํ„ด์Šค ๋‚ด๋ถ€์— ์žˆ๋Š” ์‹ค์ œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๋˜๋Š” OS ์ˆ˜์ค€์˜ ๋ฐฉํ™”๋ฒฝ์€ ๊ด€๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ EC2 ์ธ์Šคํ„ด์Šค ๋‚ด๋ถ€์—์„œ ํŠน์ • ํฌํŠธ๋‚˜ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•ด๋‹น ํฌํŠธ๋ฅผ ๊ฐœ๋ฐฉํ•˜๊ณ  ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด๋‚˜ ์„œ๋น„์Šค๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์„ค์ •์€ ๋ณด์•ˆ ๊ทธ๋ฃน๋ณด๋‹ค๋Š” ์ธ์Šคํ„ด์Šค ๋‚ด์˜ ์†Œํ”„ํŠธ์›จ์–ด ์„ค์ •๊ณผ ๊ด€๋ จ๋œ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

    โ‘ก๋กœ์ปฌ ๋ฐฉํ™”๋ฒฝ ๋˜๋Š” IP ํ…Œ์ด๋ธ” ์„ค์ •: EC2 ์ธ์Šคํ„ด์Šค ๋‚ด๋ถ€์—๋Š” Linux์˜ ๊ฒฝ์šฐ IP ํ…Œ์ด๋ธ” ๋˜๋Š” Windows์˜ ๊ฒฝ์šฐ Windows ๋ฐฉํ™”๋ฒฝ๊ณผ ๊ฐ™์€ ๋กœ์ปฌ ๋ฐฉํ™”๋ฒฝ์ด ์กด์žฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋กœ์ปฌ ๋ฐฉํ™”๋ฒฝ์€ ํŠธ๋ž˜ํ”ฝ์„ ์ œ์–ดํ•˜๊ณ  ํŠน์ • ํฌํŠธ๋ฅผ ์ฐจ๋‹จํ•˜๊ฑฐ๋‚˜ ํ—ˆ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋•Œ๋•Œ๋กœ ๋ณด์•ˆ ๊ทธ๋ฃน์˜ ๊ทœ์น™๊ณผ ๋กœ์ปฌ ๋ฐฉํ™”๋ฒฝ ์„ค์ •์ด ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, EC2 ์ธ์Šคํ„ด์Šค ๋‚ด์˜ ๋กœ์ปฌ ๋ฐฉํ™”๋ฒฝ ์„ค์ •์„ ํ™•์ธํ•˜์—ฌ ํฌํŠธ๊ฐ€ ์ œ๋Œ€๋กœ ์—ด๋ ค ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

    ์š”์•ฝํ•˜๋ฉด, โญ๋ณด์•ˆ ๊ทธ๋ฃน์€ AWS์˜ ๋„คํŠธ์›Œํฌ ๊ณ„์ธต์—์„œ ์ž‘๋™ํ•˜๋ฉฐ ํŠธ๋ž˜ํ”ฝ์„ ์ œ์–ดํ•˜๋Š” ์—ญํ• ์„ ํ•˜์ง€๋งŒ, EC2 ์ธ์Šคํ„ด์Šค ๋‚ด๋ถ€์˜ ์‹ค์ œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๋˜๋Š” ๋ฐฉํ™”๋ฒฝ ์„ค์ •์€ ๊ฐœ๋ณ„์ ์œผ๋กœ ๊ด€๋ฆฌ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๋‘ ๊ฐ€์ง€ ์„ค์ •์ด ์ผ์น˜ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๊ณ , ์ด๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ํ•„์š”ํ•œ ์„ค์ •์„ ์กฐ์ •ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Ubuntu์—์„œ ํฌํŠธ ํ—ˆ์šฉ

# 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
  • UFW๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํฌํŠธ๋ฅผ ํ—ˆ์šฉํ•˜๋ฉด ํ•ด๋‹น ํฌํŠธ๋กœ์˜ ์ธ๋ฐ”์šด๋“œ ์—ฐ๊ฒฐ์ด ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” ๋กœ์ปฌ ๋ฐฉํ™”๋ฒฝ ์„ค์ •์—๋งŒ ํ•ด๋‹นํ•˜๋ฉฐ, AWS๋‚˜ ๋‹ค๋ฅธ ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ์—์„œ๋Š” ํ•ด๋‹น ํฌํŠธ์˜ ์ธ๋ฐ”์šด๋“œ ๊ทœ์น™๋„ ๋ณ„๋„๋กœ ํ—ˆ์šฉํ•ด์•ผํ•œ๋‹ค.
  • ํฌํŠธ๋ฅผ ์—ด ๋•Œ ๋ณด์•ˆ์„ ์œ„ํ•ด ์‹ค์ œ๋กœ ํ•„์š”ํ•œ ํฌํŠธ๋งŒ ์—ด๋„๋ก ์œ ์˜ํ•œ๋‹ค.

Ubuntu์—์„œ IP ํ…Œ์ด๋ธ” ์„ค์ •

iptables ๊ฐœ๋… ๋ฐ ๋ช…๋ น์–ด (์‹คํ–‰๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์œ„ํ•ด ๋Œ€๋ถ€๋ถ„ ๋ช…๋ น์–ด์— sudo ๋ถ™์—ฌ์•ผํ•จ.)
[AWS] Ubuntu22.04 ํ™˜๊ฒฝ EC2์—์„œ ์Šคํ”„๋ง๋ถ€ํŠธ ๋Ÿฐ์นญํ•˜๊ธฐ
AWS ์„œ๋ฒ„ ํฌํŠธ Openํ•˜๊ธฐ

ํฌํŠธ ํฌ์›Œ๋”ฉ

[AWS] EC2 ํฌํŠธ ํฌ์›Œ๋”ฉ

EC2 ํฌํŠธ๋ฒˆํ˜ธ ์—ด๊ธฐ



๊ธฐํƒ€

์Šคํ”„๋ง ํ”„๋กœ์ ํŠธ application.yml ์„ค์ •

  • 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}
profile
9์—์„œ 0์œผ๋กœ, ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ๋ธ”๋กœ๊ทธ

0๊ฐœ์˜ ๋Œ“๊ธ€