<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>sftblw의 코드 공간</title>
    <link>https://sftblw.tistory.com/</link>
    <description>Ch. (aka sftblw)의 코딩 / 기술 블로그.</description>
    <language>ko</language>
    <pubDate>Tue, 26 May 2026 23:35:47 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Ch.</managingEditor>
    <image>
      <title>sftblw의 코드 공간</title>
      <url>https://tistory1.daumcdn.net/tistory/1340574/attach/1b7564ffa8384056b02d2884660ca101</url>
      <link>https://sftblw.tistory.com</link>
    </image>
    <item>
      <title>Rust가 좋다고 주장하는 이유 - 메모리 안정성은 당연하고, 그 외의 것들</title>
      <link>https://sftblw.tistory.com/109</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;rewritten in 2023.12.14&lt;/p&gt;
&lt;h1&gt;좋아요 1위의 언어, 안전한 거 말고 다른 건?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust 진짜 좋아요. 정말 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택오버플로우에서 5년 연속으로 가장 사랑받는 언어 1위가 되기도 하고, 업계 도입률과 순위도 점차 올라가고 있습니다. 아직은 살짝은 이르지만요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 매번 Rust가 좋다고 얘기하고 싶어합니다. 하지만 많은 글들이 &quot;메모리 안전성&quot;에 중점을 두고 설명하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그게 전부는 아닙니다. 그래서 저는 다른 부분에 우선 초점을 맞춰서 설명하고 싶었습니다.&lt;/p&gt;
&lt;h1&gt;C++에서 할만한 실수들이 방지된다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust를 사용하면 C++에서 할만한 실수들의 많은 수를 미연에 방지할 수 있습니다. 그건 당연한 겁니다. 입 아플 정도로 많은 곳에서 설명하고 있죠. 저는 굳이 설명하지 않겠습니다.&lt;/p&gt;
&lt;h1&gt;제로 코스트 추상화로 높은 생산성과 성능을 동시에&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust는 매우 추상화 수준이 높으며, 필요하면 낮은 추상화 수준에서도 작업할 수 있습니다. 추상화 수준이 높다는 건 생산성이 높다는 것을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상화 수준을 높여서 작업하면 느려지지 않냐구요? 아뇨, 몇몇 경우에는 추상화 수준이 더 높은 게 더 빠릅니다. &lt;a href=&quot;https://doc.rust-lang.org/book/ch13-04-performance.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;배열을 이터레이터로 순회했는데 for문보다 빠른 언어는 Rust가 거의 유일할 겁니다.&lt;/a&gt; Rust의 컴파일러는 LLVM 툴체인을 쓰는데 LLVM의 최적화 때문에 for문보다 iterator가 더 빠를 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 영역이 C++이 해결하려고 했던 영역이며, 개발자는 Rust가 안전을 위해 요구하는 규칙만 잘 지킨다면 대체로 공짜로 추상화를 달성할 수 있습니다. 날먹 최고!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익숙한 개념인 클로저나 이터레이터를 사용할수도 있고, &lt;a href=&quot;https://sftblw.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;함수형 언어에 있던 Maybe 같은 개념이 녹아있는 뛰어난 Enum&lt;/a&gt;을 사용할수도 있습니다. 상속 대신 Trait이 있어서, 다이아몬드를 고민하는 대신에 구조체에 내 함수를 덧붙일 고민만 하면 됩니다. 경우에 따라 다르겠지만 대부분이 제로 코스트이므로 열심히 추상화한다고 해서 성능이 떨어질 걱정은 좀 덜 해도 됩니다.&lt;/p&gt;
&lt;h1&gt;Go는 비교대상이 아니다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go는 네트워크 프로그래밍에 강하다고 알려져있죠. Go에는 GC가 있습니다. Garbage Collector. 메모리 할당을 관리해주는 청소부죠. 이것 때문에 &lt;a href=&quot;https://discord.com/blog/why-discord-is-switching-from-go-to-rust&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;한 끗 차이의 성능이 중요한 곳에서는 Go를 쓰기 어렵습니다&lt;/a&gt;. 이것도 원래 C++이 사용되던 영역이죠. 메모리 관리를 감당할 수 없다면 Go를 쓰는 게 낫습니다.&lt;/p&gt;
&lt;h1&gt;라이브러리를 가져다 쓰기 편하다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js 나 Python, Go 같은 다른 최신 언어들을 써보신 분들이라면 패키지 매니저에 익숙하실 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드 복사 붙여넣기는 이제 안녕. Cargo.toml 에 버전이나 Git URL 등을 명시하면 cargo가 &lt;a href=&quot;https://crates.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;crates.io&lt;/a&gt; 등등에서 소스코드를 가져와서 빌드까지 해줍니다.&lt;/p&gt;
&lt;h1&gt;어려워도, 컴파일러가 도와주니까.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서 메모리 때문에 실수할만한 요소들이 Rust에서는 컴파일이 안 된다는 말은, C++에서 실수할 만한 부분들이 뭔지 알고 이해하고 있어야 하며 그것이 Rust 언어에 녹아있다는 뜻입니다. 다시 말해, C++을 기존에 어느정도 써보지 않았다면 어렵다는 말입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렵다는 걸 개발진들도 인지하고 있어서, Rust 컴파일러는 초창기부터 오류 메시지를 친절하게 만드려고 노력했습니다. 자주 나오는 문제는 어디가 문제이고 어디를 이렇게 수정해야한다... 까지 알려주죠. C++의 템플릿 오류 메시지와 비교해보면 천국입니다.&lt;/p&gt;
&lt;h1&gt;매크로, 컴파일러와 IDE 친화적인.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++의 매크로는 전처리기가 코드를 1:1로 대치하는 것이었다면, Rust의 매크로는 컴파일러가 인지하는 코드의 단위인 토큰 단위로 동작합니다. 머리아픈 건 다를 바 없지만 IDE 지원 등의 면에서 좀 더 유리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++보다 좀 더 제약이 걸려있는 건 단점?이지만요. 애초에 전처리기에서 미리 처리한다는 발상부터가 관리하기 힘들어지는 주요 원인이 아닐까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 매크로는 선언형으로 만들 수 있고, 복잡한 매크로는 별도의 라이브러리를 만들어서 마치 컴파일러의 플러그인처럼 돌아가게 할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;문서화와 테스트조차도 내장&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대적인 언어 답게, 유닛 테스트는 코드와 같은 파일에 바로 쓸 수도 있고, 문서로는 마크다운이 기본입니다. docs.rs 에서 오픈소스 라이브러리의 문서를 둘러볼수도 있고, 명령어로 내 코드의 문서를 만들어볼수도 있습니다.&lt;/p&gt;
&lt;h1&gt;날로 먹는 크로스 컴파일&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;툴체인을 관리해주는 &quot;rustup&quot; 프로젝트를 공식에서 제공해주며, 툴체인만 추가해주면 크로스 컴파일도 손쉽게 달성할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;넓은 범위의 이용성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC 없이, C++에서 할 만한 실수를 피하고, C++급의 생산성을 달성할 수 있는 언어는 Rust가 거의 유일합니다. 필요하다면 unsafe이지만 C언어 급의 로우레벨 엑세스도 가능하죠. 이렇게 된 이상, 이용 범위가 넓어지는 건 당연한 수순입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://wasmer.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;웹어셈&lt;/a&gt;&lt;a href=&quot;https://wasmtime.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;블리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rust-embedded&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;임베디드&lt;/a&gt; (잘 모르는 분야이고, 개인적으로는 아직 좀 이르다고 생각하지만 &lt;a href=&quot;https://pmnxis.github.io/posts/my_first_commerical_rust_embedded_product_1/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;실사례가 존재합니다.&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arewegameyet.rs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;게임&lt;/a&gt; (&lt;a href=&quot;https://bevyengine.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;bevy&lt;/a&gt;가 유망주입니다)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.arewewebyet.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;웹개발&lt;/a&gt; (&lt;a href=&quot;https://github.com/tokio-rs/axum&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;백엔드&lt;/a&gt;, 심지어 &lt;a href=&quot;https://dioxuslabs.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프론트엔드&lt;/a&gt; 조차도)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://areweguiyet.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GUI&lt;/a&gt; (자라는 중)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.arewelearningyet.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;인공&lt;/a&gt;&lt;a href=&quot;https://github.com/huggingface/tokenizers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지&lt;/a&gt;&lt;a href=&quot;https://burn.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;능&lt;/a&gt; (자라는 중)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기타등등...&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 유용한 언어인데, 오늘 한 번 Rust를 배워보는 건 어떨까요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://google.github.io/comprehensive-rust/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android 팀의 4일짜리 코스&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/training/paths/rust-first-steps/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Microsoft Learn 플랫폼의 초급 교육 자료&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.rust-kr.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://doc.rust-kr.org/&lt;/a&gt; (커뮤니티 번역, 공식: &lt;a href=&quot;https://doc.rust-lang.org/book/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://doc.rust-lang.org/book/&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>특정 언어 관련/Rust</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/109</guid>
      <comments>https://sftblw.tistory.com/109#entry109comment</comments>
      <pubDate>Thu, 14 Dec 2023 15:00:46 +0900</pubDate>
    </item>
    <item>
      <title>C++ CMake 프로젝트 + Rust 프로젝트 연동 방법</title>
      <link>https://sftblw.tistory.com/93</link>
      <description>&lt;h1&gt;연동 방법&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CMake C++ 프로젝트와 Rust를 연동하는 방법은 몇 가지가 있음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바인딩 코드 자동 생성 관점에서...
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C -&amp;gt; Rust 바인딩 코드 자동생성: bindgen&lt;/li&gt;
&lt;li&gt;C &amp;lt;- Rust 바인딩 코드 자동생성: cbindgen&lt;/li&gt;
&lt;li&gt;C++ &amp;lt;-&amp;gt; Rust 바인딩 코드 자동생성: cxx (dtolnay), autocxx (Google)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;누가 빌드하냐 관점에서
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;rust-lang/cmake&lt;br /&gt;: Rust 프로젝트에서 CMake 호출&lt;/li&gt;
&lt;li&gt;corrosion (&lt;a href=&quot;https://github.com/corrosion-rs/corrosion&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/corrosion-rs/corrosion&lt;/a&gt;)&lt;br /&gt;: CMake에서 Rust 프로젝트를 타겟으로 등록, 빌드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;링킹 문제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 exe를 빌드하는 주체에 따라서 링킹 문제가 꼬임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천하는 건 Rust에서는 구멍송송난 static 라이브러리를 만든 뒤, 래핑하는 별도의 C++ CMake 바이너리에서 Rust 라이브러리를 링크하는 방식.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;링킹 관점에서 필요한 것:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Rust에서 최종 링킹한다면: C++ 프로젝트에서 의존하는 모든 것 (머리아프다)&lt;/li&gt;
&lt;li&gt;C++ CMake에서 최종 링킹한다면: Rust 표준 라이브러리가 요구하는 리스트만 들어가면 됨&lt;br /&gt;(그것조차도 Corrosion이 대신 해결해줌)&lt;br /&gt;(수동으로 한다면 &lt;a href=&quot;https://users.rust-lang.org/t/print-native-static-libs/57420&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;cargo rustc -q -- --print=native-static-libs&lt;/a&gt; 를 찍어본 다음에 수동으로 추가할 것)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Corrosion을 쓰되, Rust 프로젝트도 단독으로 라이브러리는 빌드되도록 하는 방식이 IDE 지원 면에서도 좋음.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 사용할 bindgen 라이브러리를 설정해서 build.rs를 열심히 짠다 (staticlib 설정은 필수, 바이너리는 앞에서 언급한 링크 문제가 있으니 만들지 말 것)&lt;/li&gt;
&lt;li&gt;Corrosion을 사용, Rust 프로젝트를 CMake 타겟으로 설정해서 C++ binary (exe) 타겟에 추가함&lt;/li&gt;
&lt;li&gt;PROFIT&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 정보 (2020-07-29):&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://flames-of-code.netlify.app/blog/rust-and-cmake-cplusplus/&quot;&gt;flames-of-code.netlify.app/blog/rust-and-cmake-cplusplus/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 링크 가이드는 bindgen 은 사용하지 않음.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;install 타겟&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CMake C++ -&amp;gt; C -&amp;gt; Rust + bindgen 로 래핑하는 걸 짰었는데 실패했었고, 그 이유는 &lt;b&gt;&lt;u&gt;install 타겟이 없어서&lt;/u&gt;&lt;/b&gt;였던걸로.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위 글 읽어보니 install 타겟은 의존성 있는 라이브러리를 다 설치한다고. 없으니까 당연히 링크 에러 나지...)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;null 포인터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://doc.rust-lang.org/std/primitive.pointer.html#method.is_null&quot;&gt;is_null (제일 이해하기 쉬운 방법)&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;libc++ 어쩌죠?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;println!&lt;/span&gt;(&lt;span&gt;&quot;cargo:rustc-link-lib=static=stdc++&quot;&lt;/span&gt;)&lt;span&gt;;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;dylib도 되고(이게 일반적) 나이틀리에는 minimize 하는 것도 있댔는데 찾아보긴 번거롭네요.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;cmake 빌드결과는 어디에?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rust쪽 증분 빌드에.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;target/debug/build/my_rust_bin_proj-해시 둘 중 하나 (나머지 하나는 build.rs 용)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디버깅 잘 돼요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네 (기대 안 했는데 잘 됨 잼)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이게 궁금해서 직접 해봤는데 CLion에서 브포도 잘 잡히고 뒤질 때도 잘 잡힙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 bindgen 을 안 쓰니까 C++ 소스 바뀐 걸 감지를 못 하네요. 이거 방법 있다고 인터넷에 나와있었는데 지금 찾아보긴 쫌&lt;/p&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oMhnm/btqF9C5mE43/WaK4fqaKS1CMdQC7wfXsG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oMhnm/btqF9C5mE43/WaK4fqaKS1CMdQC7wfXsG1/img.png&quot; style=&quot;width: 58.362%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;1080&quot; data-origin-width=&quot;1917&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oMhnm/btqF9C5mE43/WaK4fqaKS1CMdQC7wfXsG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoMhnm%2FbtqF9C5mE43%2FWaK4fqaKS1CMdQC7wfXsG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1917&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK5dqf/btqF6CMLLXd/kJPo54uzQkb5kK4zEt4ZZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK5dqf/btqF6CMLLXd/kJPo54uzQkb5kK4zEt4ZZ1/img.png&quot; style=&quot;width: 40.4753%;&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;1000&quot; data-origin-width=&quot;1231&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK5dqf/btqF6CMLLXd/kJPo54uzQkb5kK4zEt4ZZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK5dqf%2FbtqF6CMLLXd%2FkJPo54uzQkb5kK4zEt4ZZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1231&quot; height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;잘 되네요&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 내놔&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드...드리겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/sftblw/_cpp_rs_interop_test_proj&quot;&gt;github.com/sftblw/_cpp_rs_interop_test_proj&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;...객체는 어떻게 하나요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 열심히 찾아봤었는데 이번엔 테스트 안 해봤네요 그러고보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;see: &lt;a href=&quot;https://rust-lang.github.io/rust-bindgen/opaque.html&quot;&gt;rust-lang.github.io/rust-bindgen/opaque.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>특정 언어 관련/Rust</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/93</guid>
      <comments>https://sftblw.tistory.com/93#entry93comment</comments>
      <pubDate>Fri, 1 Dec 2023 13:39:28 +0900</pubDate>
    </item>
    <item>
      <title>oci oke 노드풀에 longhorn을 추가하기 위해 블록 볼륨을 자동으로 만들자</title>
      <link>https://sftblw.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;longhorn: 저장공간 추상화. storageClass를 oci-bv 로 했다가 노드별로 블록 볼륨이 생기는 대참사를 겪지 않을 수 있습니다. 프리티어는 전부 해서 200기가로 제한되어있으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 longhorn을 설치하면 시스템 볼륨을 사용하게 됩니다. 그건 싫으니까, 노드풀의 노드에 자동으로 별도의 블록 볼륨을 넣어주고 싶죠. 블록 볼륨은 자동으로 삭제되지 않으니까 데이터 보존 면에서도 혹시나 모를 사태를 방지할수도 있구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 가이드에서 언급하는 longhorn 은 리전간 백업을 목적으로 하는 것 같지만 전 그런 건 아무래도 상관없구요... &lt;i&gt;춘천이 불바다나면 내 데이터는 어차피 끝장이야~&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 가이드가 있는데, 어딘가 나사가 빠진 부분이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/learn/deploy-longhorn-on-oke/index.html#prerequisites&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/en/learn/deploy-longhorn-on-oke/index.html#prerequisites&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 파이썬 스크립트의 들여쓰기가 잘못되어 있습니다&lt;/b&gt;. 이건 적당히 고치면 되는거고&lt;/p&gt;
&lt;pre id=&quot;code_1688813108886&quot; class=&quot;shell&quot; style=&quot;scroll-behavior: auto !important;&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash
curl --fail -H &quot;Authorization: Bearer Oracle&quot; -L0 http://169.254.169.254/opc/v2/instance/metadata/oke_init_script | base64 --decode &amp;gt;/var/run/oke-init.sh
bash /var/run/oke-init.sh

echo &quot;installing python3-pip , oci sdk\n&quot;
sudo yum install python3 -y
sudo yum install python3-pip -y
pip3 install oci
pip3 install requests

cat &amp;lt;&amp;lt; EOF &amp;gt; pyscript.py
#!/usr/bin/python

import oci
import requests

size_in_gbs = 50
vpus_per_gb = 10
mode = 'PARA'
device_path = &quot;/dev/oracleoci/oraclevdb&quot;
signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()

def get_current_instance_details():
    r = requests.get(url= 'http://169.254.169.254/opc/v2/instance', headers={&quot;Authorization&quot;: &quot;Bearer Oracle&quot;})
    return r.json()

instanceDetails = get_current_instance_details()

compute_client = oci.core.ComputeClient({&quot;region&quot;: instanceDetails[&quot;region&quot;]}, signer = signer)
block_storage_client = oci.core.BlockstorageClient({&quot;region&quot;: instanceDetails[&quot;region&quot;]}, signer = signer)


def create_volume(block_storage, compartment_id, availability_domain, display_name: str):
    print(&quot;--- creating block volume ---&quot;)
    result = block_storage.create_volume(
        oci.core.models.CreateVolumeDetails(
            compartment_id=compartment_id,
            availability_domain=availability_domain,
            display_name=display_name,
            size_in_gbs = size_in_gbs,
            vpus_per_gb = vpus_per_gb
        )
    )
    volume = oci.wait_until(
        block_storage,
        block_storage.get_volume(result.data.id),
        'lifecycle_state',
        'AVAILABLE'
    ).data
    print('--- Created Volume ocid: {} ---'.format(result.data.id))
    return volume

def attach_volume(instance_id, volume_id,device_path):
    volume_attachment_response = &quot;&quot;
    if mode == 'ISCSI':
        print(&quot;--- Attaching block volume {} to instance {}---&quot;.format(volume_id,instance_id))
        volume_attachment_response = compute_client.attach_volume(
            oci.core.models.AttachIScsiVolumeDetails(
                display_name='IscsiVolAttachment',
                instance_id=instance_id,
                volume_id=volume_id,
                device= device_path
                )
            )
    elif mode == 'PARA':
        volume_attachment_response = compute_client.attach_volume(
            oci.core.models.AttachParavirtualizedVolumeDetails(
            display_name='ParavirtualizedVolAttachment',
            instance_id=instance_id,
            volume_id=volume_id,
            device= device_path
        )
    )
    oci.wait_until(
        compute_client,
        compute_client.get_volume_attachment(volume_attachment_response.data.id),
        'lifecycle_state',
        'ATTACHED'
    )
    print(&quot;--- Attaching complete block volume {} to instance {}---&quot;.format(volume_id,instance_id))
    print(volume_attachment_response.data)

# Call instance metadata uri to get current instace details

volume = create_volume(block_storage= block_storage_client, compartment_id= instanceDetails['compartmentId'], availability_domain=instanceDetails['availabilityDomain'], display_name= instanceDetails['displayName'])
attach_volume(instance_id=instanceDetails['id'], volume_id=volume.id, device_path= device_path)

EOF

echo &quot;running python script\n&quot;
chmod 755 pyscript.py
./pyscript.py

echo &quot;creating file system on volume\n&quot;
sudo /sbin/mkfs.ext4 /dev/oracleoci/oraclevdb
echo &quot;mounting volume\n&quot;
sudo mkdir /mnt/volume
sudo mount /dev/oracleoci/oraclevdb /mnt/volume
echo &quot;adding entry to fstab\n&quot;
echo &quot;/dev/oracleoci/oraclevdb /mnt/volume ext4 defaults,_netdev,nofail 0 2&quot; |  sudo tee -a /etc/fstab&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 노드풀의 노드에 권한을 부여해줘야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 스크립트는 Instance Principals 라는 인증방법을 사용하고 있는데, 노드에 부여되는 인증방법인 셈이죠.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;169.254.169.254 주소는 cloud-init 의 표준적인 주소입니다. 그냥 제공되는 거니까 신경 안 쓰셔도 됩니다.&lt;/li&gt;
&lt;li&gt;원본 가이드의 스크립트에는 v1 HTTP API 가 기술되어 있는데 문서 보니까 v2 나왔다길래 바꿔보니까 쓰는 부분은 영향이 없더라구요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 Dynamic Group (동적 그룹) 을 만들어서 노드를 매칭시킬 ID같은 걸 만들고, 이 동적 그룹에 권한을 부여해줘야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compartment (구획): 개념적인 리소스 분할의 단위입니다. 루트 테넌시가 루트 구획(Compartment) 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dynamic Group 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID -&amp;gt; 도메인 (우측 위 메뉴에서 현재 도메인 선택) -&amp;gt; 동적 그룹&lt;/p&gt;
&lt;pre id=&quot;code_1688813723045&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;All {instance.compartment.id='ocid1.tenancy.oc1..그냥테넌시넣음',tag.태그를.미리부여해놨습니다.value='yes'}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en-us/iaas/Content/Identity/dynamicgroups/Writing_Matching_Rules_to_Define_Dynamic_Groups.htm#Writing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/en-us/iaas/Content/Identity/dynamicgroups/Writing_Matching_Rules_to_Define_Dynamic_Groups.htm#Writing&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적 취향으로 태그를 부여해놓아야겠다 싶어서 노드 풀의 설정에서 노드에 자동으로 태그를 부여하게 해놨습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVbc4P/btsmO4HndAC/LUZvPqk3h8qmYWQdCKgIRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVbc4P/btsmO4HndAC/LUZvPqk3h8qmYWQdCKgIRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVbc4P/btsmO4HndAC/LUZvPqk3h8qmYWQdCKgIRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVbc4P%2FbtsmO4HndAC%2FLUZvPqk3h8qmYWQdCKgIRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;289&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱히 태그의 이름이나 값에 의미가 있지는 않습니다. &quot;.value&quot; 부분은 킵하는 게 정답이더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권한 부여 (정책 / Policy)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 셀렉팅은 했으니 노드에 권한을 부여해줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688813974332&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Allow dynamic-group 'Default'/'내_동적_그룹' to manage volume-family in tenancy
Allow dynamic-group 'Default'/'내_동적_그룹' to use instance-family in tenancy
Allow dynamic-group 'Default'/'내_동적_그룹' to manage volume-attachments in tenancy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충해서 맞는지는 몰?루겠네요. 앞의 2개는 규칙 작성기로 만들었고 나머지 하나만 수동으로...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volume-attachments 는 실제로 존재하는 리소스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en-us/iaas/Content/Block/Tasks/enablingblockvolumemanagementplugin.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/en-us/iaas/Content/Block/Tasks/enablingblockvolumemanagementplugin.htm&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가이드대로 마저 하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 노드풀의 cloud-init 에 위의 스크립트를 (용량 같은 거 확인해보고) 붙여넣으신 뒤 만드시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceVWB8/btsmROjePUi/tDUPn8QvC5nIiy1vgRSXAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceVWB8/btsmROjePUi/tDUPn8QvC5nIiy1vgRSXAK/img.png&quot; data-alt=&quot;내가 인간 승리자다!!!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceVWB8/btsmROjePUi/tDUPn8QvC5nIiy1vgRSXAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceVWB8%2FbtsmROjePUi%2FtDUPn8QvC5nIiy1vgRSXAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1782&quot; height=&quot;361&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내가 인간 승리자다!!!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노드 트러블슈팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bastion 연결은 여러차례 실패해서... 포기하고 cloud-shell 에서 일봤습네다. 노드가 있는 전용망을 선택하고, 노드 풀의 트러블슈팅 메뉴에서 노드를 만들 때 썼던 ssh 키로 접속하면 이것저것 실행해볼 수 있습니다. 다른 방법은 다 실패함...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;330&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8AAvA/btsmRn0s1mW/ChWC08pHOyhGUq73fm1BNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8AAvA/btsmRn0s1mW/ChWC08pHOyhGUq73fm1BNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8AAvA/btsmRn0s1mW/ChWC08pHOyhGUq73fm1BNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8AAvA%2FbtsmRn0s1mW%2FChWC08pHOyhGUq73fm1BNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;330&quot; height=&quot;182&quot; data-origin-width=&quot;330&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내 bastion이 ssh-rsa를 안 받아요!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/.ssh/config&lt;/p&gt;
&lt;pre id=&quot;code_1688815043938&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Host *.oci.oraclecloud.com
    PubkeyAcceptedKeyTypes=+ssh-rsa
    HostKeyAlgorithms=+ssh-rsa&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오라클-감사합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://stackoverflow.com/a/74258486/4394750&quot;&gt;https://stackoverflow.com/a/74258486/4394750&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ChatGPT에게 도움을 받은 흔적&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해의 명예 오라클 직원상을 줄 수 있다면 GPT4에게 주고 싶네요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba6WHJ/btsmPGM0SmM/ZUpe23Lv4b5SZpbhUCR970/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba6WHJ/btsmPGM0SmM/ZUpe23Lv4b5SZpbhUCR970/img.png&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;994&quot; data-is-animation=&quot;false&quot; style=&quot;width: 31.5021%; margin-right: 10px;&quot; data-widthpercent=&quot;32.25&quot; width=&quot;904&quot; height=&quot;994&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba6WHJ/btsmPGM0SmM/ZUpe23Lv4b5SZpbhUCR970/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba6WHJ%2FbtsmPGM0SmM%2FZUpe23Lv4b5SZpbhUCR970%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lcMR0/btsmOMNJsKU/svuWMAhkxurK809uFKFBi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lcMR0/btsmOMNJsKU/svuWMAhkxurK809uFKFBi0/img.png&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;979&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.0556%; margin-right: 10px;&quot; data-widthpercent=&quot;32.82&quot; width=&quot;906&quot; height=&quot;979&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lcMR0/btsmOMNJsKU/svuWMAhkxurK809uFKFBi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlcMR0%2FbtsmOMNJsKU%2FsvuWMAhkxurK809uFKFBi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;906&quot; height=&quot;979&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RlJBc/btsmRNLn2kB/qOG5HdsCpkTY4t2xGin571/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RlJBc/btsmRNLn2kB/qOG5HdsCpkTY4t2xGin571/img.png&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;996&quot; data-is-animation=&quot;false&quot; style=&quot;width: 34.1167%;&quot; data-widthpercent=&quot;34.93&quot; width=&quot;981&quot; height=&quot;996&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RlJBc/btsmRNLn2kB/qOG5HdsCpkTY4t2xGin571/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRlJBc%2FbtsmRNLn2kB%2FqOG5HdsCpkTY4t2xGin571%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;981&quot; height=&quot;996&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjcU2N/btsmWlAzMh9/ULtRm81NkasW5UwU494kv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjcU2N/btsmWlAzMh9/ULtRm81NkasW5UwU494kv0/img.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;906&quot; data-is-animation=&quot;false&quot; style=&quot;width: 25.7714%; margin-right: 10px;&quot; data-widthpercent=&quot;26.39&quot; width=&quot;866&quot; height=&quot;906&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjcU2N/btsmWlAzMh9/ULtRm81NkasW5UwU494kv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjcU2N%2FbtsmWlAzMh9%2FULtRm81NkasW5UwU494kv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;866&quot; height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKorPE/btsmO4HnS1S/r0khWP4eRl00EPlNNZ1YIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKorPE/btsmO4HnS1S/r0khWP4eRl00EPlNNZ1YIk/img.png&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;990&quot; data-is-animation=&quot;false&quot; style=&quot;width: 24.1022%; margin-right: 10px;&quot; data-widthpercent=&quot;24.68&quot; width=&quot;885&quot; height=&quot;990&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKorPE/btsmO4HnS1S/r0khWP4eRl00EPlNNZ1YIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKorPE%2FbtsmO4HnS1S%2Fr0khWP4eRl00EPlNNZ1YIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;885&quot; height=&quot;990&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCRVon/btsmOKh8qL3/ai1geMxw7hnJPjdIxxVM41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCRVon/btsmOKh8qL3/ai1geMxw7hnJPjdIxxVM41/img.png&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;502&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.8008%;&quot; data-widthpercent=&quot;48.93&quot; width=&quot;890&quot; height=&quot;502&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCRVon/btsmOKh8qL3/ai1geMxw7hnJPjdIxxVM41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCRVon%2FbtsmOKh8qL3%2Fai1geMxw7hnJPjdIxxVM41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;890&quot; height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2IBUD/btsmWl8p2Px/WD3YYKNr9eGWN9jFajmcF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2IBUD/btsmWl8p2Px/WD3YYKNr9eGWN9jFajmcF0/img.png&quot; data-origin-width=&quot;923&quot; data-origin-height=&quot;728&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.5298%; margin-right: 10px;&quot; data-widthpercent=&quot;36.38&quot; width=&quot;923&quot; height=&quot;728&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2IBUD/btsmWl8p2Px/WD3YYKNr9eGWN9jFajmcF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2IBUD%2FbtsmWl8p2Px%2FWD3YYKNr9eGWN9jFajmcF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;923&quot; height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OnmRj/btsmR91KoZH/TpkiJ3Xn0jaMoYhnsRB9b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OnmRj/btsmR91KoZH/TpkiJ3Xn0jaMoYhnsRB9b0/img.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;997&quot; data-is-animation=&quot;false&quot; style=&quot;width: 22.2614%; margin-right: 10px;&quot; data-widthpercent=&quot;22.79&quot; width=&quot;792&quot; height=&quot;997&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OnmRj/btsmR91KoZH/TpkiJ3Xn0jaMoYhnsRB9b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOnmRj%2FbtsmR91KoZH%2FTpkiJ3Xn0jaMoYhnsRB9b0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;792&quot; height=&quot;997&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czGMui/btsmRm1z30K/KU1Vxt8mC6bGtqMTdvNY20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czGMui/btsmRm1z30K/KU1Vxt8mC6bGtqMTdvNY20/img.png&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;612&quot; data-is-animation=&quot;false&quot; style=&quot;width: 39.8832%;&quot; data-widthpercent=&quot;40.83&quot; width=&quot;871&quot; height=&quot;612&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czGMui/btsmRm1z30K/KU1Vxt8mC6bGtqMTdvNY20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczGMui%2FbtsmRm1z30K%2FKU1Vxt8mC6bGtqMTdvNY20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;871&quot; height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lcWPe/btsmRsf9Vy2/L9qRv9q2gBBIK5gXUwkoQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lcWPe/btsmRsf9Vy2/L9qRv9q2gBBIK5gXUwkoQK/img.png&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;551&quot; data-is-animation=&quot;false&quot; style=&quot;width: 45.9948%; margin-right: 10px;&quot; data-widthpercent=&quot;47.09&quot; width=&quot;859&quot; height=&quot;551&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lcWPe/btsmRsf9Vy2/L9qRv9q2gBBIK5gXUwkoQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlcWPe%2FbtsmRsf9Vy2%2FL9qRv9q2gBBIK5gXUwkoQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;859&quot; height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6XPUk/btsmPzOo4Bk/aKCxvuDnqBsQ3k5ZiKjtP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6XPUk/btsmPzOo4Bk/aKCxvuDnqBsQ3k5ZiKjtP1/img.png&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;997&quot; data-is-animation=&quot;false&quot; style=&quot;width: 26.0704%; margin-right: 10px;&quot; data-widthpercent=&quot;26.69&quot; width=&quot;881&quot; height=&quot;997&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6XPUk/btsmPzOo4Bk/aKCxvuDnqBsQ3k5ZiKjtP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6XPUk%2FbtsmPzOo4Bk%2FaKCxvuDnqBsQ3k5ZiKjtP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;881&quot; height=&quot;997&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dT6Qh9/btsmQfBCWSa/wQ7YmJKY9fXkKLUl3cuhY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dT6Qh9/btsmQfBCWSa/wQ7YmJKY9fXkKLUl3cuhY0/img.png&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;985&quot; data-is-animation=&quot;false&quot; style=&quot;width: 25.6092%;&quot; data-widthpercent=&quot;26.22&quot; width=&quot;855&quot; height=&quot;985&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dT6Qh9/btsmQfBCWSa/wQ7YmJKY9fXkKLUl3cuhY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdT6Qh9%2FbtsmQfBCWSa%2FwQ7YmJKY9fXkKLUl3cuhY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;855&quot; height=&quot;985&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>언어 무관/리눅스, 시스어드민</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/123</guid>
      <comments>https://sftblw.tistory.com/123#entry123comment</comments>
      <pubDate>Sat, 8 Jul 2023 20:23:19 +0900</pubDate>
    </item>
    <item>
      <title>k3s 설치 (etcd HA, traefik, MetalLB L2, Longhorn)</title>
      <link>https://sftblw.tistory.com/122</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;k3s 내장 traefik 사용&lt;/s&gt;&lt;br /&gt;설정 변경이 번거로워서 따로 설치합니다.&lt;/li&gt;
&lt;li&gt;k3s 내장 ServiceLB 는 사용하지 않습니다. MetalLB L2 모드로 대체합니다.&lt;/li&gt;
&lt;li&gt;k3s 내장 etcd 로 클러스터를 만듭니다.&lt;/li&gt;
&lt;li&gt;odroid 기기들과 x86 홈서버 (그냥 PC) 를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;s&gt;저장공간은 OpenEBS + cStor 를 사용합니다.&lt;/s&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;mayastor를 쓰고싶었으나, CPU 사용률이 아직 높은 문제가 있다네요.&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Odroid 기기에는 Armbian 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사유: 공식 우분투 이미지는 커널 버전이 옛날이라 k3s 설치가 안 됨&lt;br /&gt;(cgroups v2 요구, Odroid N2의 공식 우분투 이미지는 4.x 대임)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;petitboot 로 더 최신버전 우분투를 깐다는 얘기도 있음 &lt;a href=&quot;https://www.clien.net/service/board/cm_rasp/13621153&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(1 petitboot?)&lt;/a&gt; (&lt;a href=&quot;https://forum.odroid.com/viewtopic.php?f=212&amp;amp;t=45978&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2-1&lt;/a&gt;, &lt;a href=&quot;https://forum.odroid.com/viewtopic.php?f=216&amp;amp;t=45967&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2-2&lt;/a&gt;)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ufw allow&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.k3s.io/advanced#ubuntu&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.k3s.io/advanced#ubuntu&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685506631706&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 공식 문서, ubuntu

sudo ufw allow 6443/tcp comment 'k3s apiserver'
sudo ufw allow from 10.42.0.0/16 to any comment 'k3s pods'
sudo ufw allow from 10.43.0.0/16 to any comment 'k3s services'&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685551535708&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# k3s를 위한 ufw 설정
## ChatGPT한테 좀 더 시키고, 살짝 다듬음.

################

sudo ufw allow 6443/tcp comment 'k3s apiserver'
sudo ufw allow from 10.42.0.0/16 to any comment 'k3s pods'
sudo ufw allow from 10.43.0.0/16 to any comment 'k3s services'
sudo ufw allow 2379:2380/tcp comment 'k3s etcd - server to server'
sudo ufw allow 6443/tcp comment 'k3s supervisor and Kubernetes API Server - from agents to servers'
sudo ufw allow 8472/udp comment 'k3s flannel VXLAN - all nodes'
sudo ufw allow 10250/tcp comment 'k3s kubelet metrics - all nodes'
sudo ufw allow 51820/udp comment 'k3s flannel Wireguard IPv4 - all nodes'
sudo ufw allow 51821/udp comment 'k3s flannel Wireguard IPv6 - all nodes'


# https://metallb.universe.tf/
sudo ufw allow 7946/tcp comment 'k3s metallb speaker tcp'
sudo ufw allow 7946/udp comment 'k3s metallb speaker udp'

# 추가로
# https://digitalis.io/blog/kubernetes/k3s-lightweight-kubernetes-made-ready-for-production-part-1/

sudo ufw allow from 192.168.7.0/24 to any port 3260 comment 'k3s longhorn iscsi local'

# 모든 아웃바운드 트래픽을 허용
# sudo ufw default allow outgoing comment 'Allow all outbound traffic'
# ChatGPT에 의하면 기본이 허용이라고 함. 필요없는 게 맞을듯?

##################

# ufw 설정 적용
sudo ufw enable
sudo ufw reload&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1686141073217&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 내부망에서 포트 허용 (재설치를 여러번 했네요 ㅠ)
# IP 대역과 포트는 바꾸시면 됩니다.
sudo ufw allow from 192.168.1.0/24 to any port 22&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;s&gt;traefik 80, 443 변경&lt;/s&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;k8s의 네트워킹은 특정 포트가 필요하다면 시스템의 네트워크를 앞에서 가로챕니다. (&lt;a href=&quot;https://coffeewhale.com/k8s/network/2019/04/19/k8s-network-01/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;80, 443에 있는 기존 서비스는 유지해야 하지만, traefik은 쓰고싶으므로 사전에 설정을 넣어둡시다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;&lt;a href=&quot;https://docs.k3s.io/helm#customizing-packaged-components-with-helmchartconfig&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.k3s.io/helm#customizing-packaged-components-with-helmchartconfig&lt;/a&gt;&lt;/s&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685499982122&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mkdir -p /var/lib/rancher/k3s/server/manifests/
sudo nano /var/lib/rancher/k3s/server/manifests/traefik-config-sftblw.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685499955658&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    ports:
      web:
        exposedPort: 13080
      websecure:
        exposedPort: 13443&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;k3s 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;embedded etcd HA 로 설치하려면 --cluster-init 만 붙어있으면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.k3s.io/datastore/ha-embedded&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.k3s.io/datastore/ha-embedded&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 서버&lt;/p&gt;
&lt;pre id=&quot;code_1685500333585&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | K3S_TOKEN=SFTBLW_VERY_SECRET_TOKEN sh -s - server --cluster-init --disable=servicelb --disable=traefik&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두, 세 번째 서버&lt;/p&gt;
&lt;pre id=&quot;code_1685500357843&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | K3S_TOKEN=SFTBLW_VERY_SECURE_SECRET sh -s - server --server https://&amp;lt;ip or hostname of server1&amp;gt;:6443 --disable=servicelb --disable=traefik&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 시스템에 저장되어 있으므로 오래되어서 까먹어도 상관이 없습니다. 어디있더라...&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;노드 연결 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 까먹는데, kubeconfig는 /etc/rancher/k3s/k3s.yaml 에 있습니다. ~/.kube/config 에 옮겨서 권한 설정해두시면 그냥 사용 가능. kubectl 자체에 프로필 기능도 있던 거 같은데 자세히는 안 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 그냥 sudo 로 확인합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1685551960122&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo kubectl get nodes&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685551974002&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;NAME   STATUS   ROLES                       AGE   VERSION
hc2    Ready    control-plane,etcd,master   4s    v1.26.5+k3s1
moe    Ready    control-plane,etcd,master   83s   v1.26.5+k3s1
n2     Ready    control-plane,etcd,master   30s   v1.26.5+k3s1&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;지금부터는 API랑 통신하므로,&lt;br /&gt;kubectl이 세팅된 아무 컴퓨터 한 대에서만 작업해도 됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MetalLB 설치, L2 모드 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k8s에서 로드밸런싱은 클러스터 밖의 것, 그러니까 클라우드의 로드밸런서를 씁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가정에서 그럴 순 없으니까 k3s에 내장된 ServiceLB를 쓰거나, 별도로 MetalLB를 설치해서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 방식은 다음과 같습니다. 이번엔 MetalLB L2 모드로 설치합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ServiceLB는 노드들의 IP 중 비어있는 포트의 IP를 사용합니다.&lt;/li&gt;
&lt;li&gt;MetalLB L2 모드는 ARP (IPv4) 프로토콜을 사용해 새로운 IP를 할당하고 주변에 알립니다.&lt;br /&gt;한 기기가 모든 트래픽을 받게되긴 하지만, OpenWRT가 없는 홈 네트워크에서는 나름 적합합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기기 다운 시 다른 노드에 옮겨가는데, 몇 초? 정도의 딜레이가 있다고 봤던 거 같습니다. 그보다 짧았던가? 몰?루&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OpenWRT 지원 공유기 / 비싼 라우터가 있다면 MetalLB BGP 모드를 써보고싶네요...&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MetalLB 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 helm 설치를 선호합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://metallb.universe.tf/installation/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://metallb.universe.tf/installation/&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685552610563&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm repo add metallb https://metallb.github.io/metallb
helm repo update
helm install metallb metallb/metallb --namespace metallb-system --create-namespace&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공유기의 DHCP에서 대역 비워놓기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MetalLB L2 모드가 자기네들끼리 알아서 할당해서 광고할 IP 대역을, 자동으로 할당하지 않도록 비워놓습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NX0jY/btsiaBiyJ9C/0lCmEg9XPLn4LVn3E8qn10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NX0jY/btsiaBiyJ9C/0lCmEg9XPLn4LVn3E8qn10/img.png&quot; data-alt=&quot;iptime&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NX0jY/btsiaBiyJ9C/0lCmEg9XPLn4LVn3E8qn10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNX0jY%2FbtsiaBiyJ9C%2F0lCmEg9XPLn4LVn3E8qn10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;644&quot; height=&quot;221&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;iptime&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MetalLB L2 설정 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 비워놓은 IP 대역을 리소스로 넣어줍시다. 그리고 그 바로 밑에 --- 로 구분해서 L2 광고도 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1685553080962&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: sftblw-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.7.128-192.168.7.254 # 모자이크한 의미가 없잖아...
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: sftblw-l2-advertisement
  namespace: metallb-system&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무 파일로 저장하고, 다음을 입력합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1685553130216&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# asdf.yml 로 저장했다고 가정합니다.
kubectl apply -f asdf.yml&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;traefik 설치 &amp;amp; cert-manager로 인증서 등록&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;traefik 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서부터는 처음 해봅니다. 설명이 좀 오락가락 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://traefik.io/blog/secure-web-applications-with-traefik-proxy-cert-manager-and-lets-encrypt/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://traefik.io/blog/secure-web-applications-with-traefik-proxy-cert-manager-and-lets-encrypt/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.padok.fr/en/blog/traefik-kubernetes-certmanager&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.padok.fr/en/blog/traefik-kubernetes-certmanager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;helm 차트용 traefik_values.yaml&lt;/p&gt;
&lt;pre id=&quot;code_1686830453174&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ports:
  web:
    exposedPort: 80
  websecure:
    exposedPort: 443

service:
  enabled: true
  type: LoadBalancer

  spec:
    loadBalancerIP: &quot;192.168.7.150&quot;

# 어차피 아무 상관 없겠지만 넣기로 했습니다.
autoscaling:
  enabled: true
  minReplicas: 1
  maxReplicas: 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치&lt;/p&gt;
&lt;pre id=&quot;code_1686830505278&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm repo add traefik https://traefik.github.io/charts

helm upgrade --install --namespace=traefik-system --create-namespace traefik traefik/traefik -f traefik_values.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어... 왜 host 포트 안 먹지... 아, 기본으로 host 포트를 안 먹는구나... k3s 내장 traefik 은 시스템 포트를 사용하도록 되어있었나보네요.&lt;/p&gt;
&lt;pre id=&quot;code_1686830858474&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; k get all -n traefik-system
NAME                           READY   STATUS    RESTARTS   AGE
pod/traefik-67d45866dd-4fh9r   1/1     Running   0          8m9s

NAME              TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)                      AGE
service/traefik   LoadBalancer   10.43.151.2   192.168.7.150   80:30168/TCP,443:30885/TCP   8m9s

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/traefik   1/1     1            1           8m9s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/traefik-67d45866dd   1         1         1       8m9s

NAME                                          REFERENCE            TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/traefik   Deployment/traefik   &amp;lt;unknown&amp;gt;/80%   1         3         1          31s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대시보드는 포워딩으로 들어가는 게 나을 것 같네요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://community.traefik.io/t/traefik-pod-not-found-when-using-kubectl-port-forward-for-dashboard-access/9895&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://community.traefik.io/t/traefik-pod-not-found-when-using-kubectl-port-forward-for-dashboard-access/9895&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/traefik/traefik-helm-chart/issues/85&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/traefik/traefik-helm-chart/issues/85&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1686832071562&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl port-forward -n traefik-system $(kubectl get pods --selector &quot;app.kubernetes.io/name=traefik&quot; --output=name -n traefik-system) 9000:9000

# 아래에 들어가봅니다.
localhost:9000/dashboard/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1469&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUuQI8/btsj7cmFhJm/0GjeBaocNaqBaIDRhcs0QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUuQI8/btsj7cmFhJm/0GjeBaocNaqBaIDRhcs0QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUuQI8/btsj7cmFhJm/0GjeBaocNaqBaIDRhcs0QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUuQI8%2Fbtsj7cmFhJm%2F0GjeBaocNaqBaIDRhcs0QK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1469&quot; height=&quot;721&quot; data-origin-width=&quot;1469&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신기하네요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내부망의 외부 IP로의 Ingress 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 test용 pod을 올리겠지만, 저는 기존 서비스가 있는 상황이어서 해당 포트로 서비스를 만들어 매핑을 해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;ExternalName 타입의 Service 리소스를 생성하고, 포트도 거기에 정의해줍니다. ExternalName에는 포트는 원래는 없는데, traefik의 경우 &lt;a href=&quot;https://doc.traefik.io/traefik/routing/providers/kubernetes-crd/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IngressRoute 혹은 Service 둘 중 하나 이상에 정의가 되어있어야&lt;/a&gt; ExternalName이 동작한다네요.&lt;/s&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1686834502403&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Namespace
metadata:
  name: sftblw-moe-legacy
---
apiVersion: v1
kind: Service
metadata:
  name: sftblw-moe-legacy
  namespace: sftblw-moe-legacy
spec:
  type: ExternalName
  externalName: 192.168.7.13
  ports:
    - name: websecure
      port: 443
    - name: web
      port: 80&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1686834784179&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f sftblw-moe-legacy-ingress.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다...만 이 방법은 Ingress로 라우팅할 때에는 동작하지 않는다는 모양입니다. 정확하게는 모르겠는데, 저는 (이후 설정할 Ingress에서) 다음과 같은 에러가 납니다. 아무래도 라우팅에는 &lt;a href=&quot;https://yoo11052.tistory.com/193&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&quot;Endpoints&quot; 라는 리소스가 필요&lt;/a&gt;한 모양이네요.&lt;/p&gt;
&lt;pre id=&quot;code_1687529207831&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Rules:
  Host        Path  Backends
  ----        ----  --------
  sftblw.moe  
              /   sftblw-moe-legacy-service:80 (&amp;lt;error: endpoints &quot;sftblw-moe-legacy-service&quot; not found&amp;gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, Headless Service를 만드는 방법을 사용합니다. Service와 같은 이름의 { Endpoints 혹은 EndpointSlice (더 최신) } 을 만듭니다. 셀렉터(? 뭐지?) 가 없는 Headless Service는 EndpointSlice (혹은 구버전에서는? Endpoints) 를 자동으로 만들지 않는다네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/a/57769127/4394750&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/a/57769127/4394750&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/a/61753491/4394750&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/a/61753491/4394750&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Endpoints 사용&lt;/p&gt;
&lt;pre id=&quot;code_1687529856826&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Namespace
metadata:
  name: sftblw-moe
---
apiVersion: v1
kind: Service
metadata:
  name: sftblw-moe-legacy-service
  namespace: sftblw-moe
spec:
  type: ClusterIP
  clusterIP: None
  ports:
    - name: web
      port: 80
---
apiVersion: v1
kind: Endpoints
metadata:
  name: sftblw-moe-legacy-service
subsets:
- addresses:
  - ip: 192.168.7.13
  ports:
  - name: web
    port: 80
    protocol: TCP&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EndpointSlice 사용 (&lt;a href=&quot;https://github.com/traefik/traefik/issues/8406&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다만... traefik 은 이게 안 되나봅니다. 사유는 모르겠음...&lt;/a&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1687529924943&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Namespace
metadata:
  name: sftblw-moe
---
apiVersion: v1
kind: Service
metadata:
  name: sftblw-moe-legacy-service
  namespace: sftblw-moe
spec:
  type: ClusterIP
  clusterIP: None
  ports:
    - name: web
      port: 80
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: sftblw-moe-legacy-service
  namespace: sftblw-moe
addressType: IPv4
endpoints:
  - addresses:
    - 192.168.7.13
    conditions:
      ready: true
ports:
  - name: web
    port: 80
    protocol: TCP&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내부망의 nginx https 무한 리다이렉트 트러블슈팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부망에 연결하려고 하는데, 내부망의 nginx에서 http_x_forwarded_proto 가 http가 찍혀서 무한 리다이렉트가 발생한다면 아래 옵션을 사용해볼 수도 있습니다. (저 먼 위쪽의 traefik_values.yaml 끝에 추가하고 업그레이드하세요.) ip도 확인해보시고...&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;traefik 옵션: &lt;a href=&quot;https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;관련글 1: &lt;a href=&quot;https://medium.com/@_jonas/traefik-kubernetes-ingress-and-x-forwarded-headers-82194d319b0e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/@_jonas/traefik-kubernetes-ingress-and-x-forwarded-headers-82194d319b0e&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;관련글 2: &lt;a href=&quot;https://community.traefik.io/t/eks-tls-termination-x-forwarded-for/10487&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://community.traefik.io/t/eks-tls-termination-x-forwarded-for/10487&lt;/a&gt;, 이 외에도 많음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1687536645536&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;additionalArguments:
  - &quot;--entryPoints.websecure.forwardedHeaders.trustedIPs=127.0.0.1/32,192.168.7.1/24,10.42.0.1/24&quot;
  - &quot;--entryPoints.web.proxyProtocol.insecure&quot;
  - &quot;--entryPoints.web.forwardedHeaders.insecure&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 경우, 이게 문제가 아니었습니다. mastodon 이 무한 리다이렉트를 했는데,&lt;/p&gt;
&lt;pre id=&quot;code_1687536802589&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;proxy_set_header X-Forwarded-For $scheme;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 포맷을 바꿔 확인했구요&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nginx 프록시 모듈: &lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_proxy_module.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://nginx.org/en/docs/http/ngx_http_proxy_module.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;로깅은 어떻게?&amp;nbsp; main을 넣어라: &lt;a href=&quot;https://stackoverflow.com/questions/41528360/log-format-in-nginx-conf-being-ignored&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/41528360/log-format-in-nginx-conf-being-ignored&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1687536863824&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# nginx.conf

http {
    # http_x_forwarded_proto 를 추가했습니다
	log_format main '$http_x_forwarded_for $http_x_forwarded_proto - $remote_user [$time_local] &quot;$host&quot; &quot;$request&quot; '
            '$status $body_bytes_sent &quot;$http_referer&quot; '
            '&quot;$http_user_agent&quot; $request_time';
            
    # main 을 추가해야 합니다
    access_log /var/log/nginx/access.log main;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 추가 수정 없이도 전부 https가 찍히더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT의 도움을 받아, X_Forwarded_Proto 헤더가 이미 설정된 경우 그걸 사용하도록 구성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687537051532&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;map $http_x_forwarded_proto $proxy_x_forwarded_proto {
  default $http_x_forwarded_proto;
  ''      $scheme;
}

server {
    location @proxy {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cert-manager 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cert-manager.io/docs/installation/helm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://cert-manager.io/docs/installation/helm/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;installCRDs의 CRD는 custom resource definition 으로, 인증 정보를 cert-manager가 만드는 커스텀 리소스로 정의할거라 필요합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1686832513036&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; helm upgrade --install \
     cert-manager jetstack/cert-manager \
     --namespace cert-manager --create-namespace \
     --set installCRDs=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cert-manager의 커스텀 리소스 중 Issuer는 두 종류가 있는데, 네임스페이스 로컬인 Issuer와 클러스터 전역인 ClusterIssuer가 있습니다. 전부 LetsEncrypt로 할거니까 ClusterIssuer를 만들거구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LetsEncrypt의 프로덕션용 인증은 rate limit이 짜다고 합니다. 그래서 먼저 staging으로 추가해서 테스트한 뒤, production 으로 바꿔주겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1686889078793&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    email: myemail@example.com
    privateKeySecretRef:
      name: letsencrypt-staging
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    solvers:
    - http01:
        ingress:
          class: traefik&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Ingress 혹은 IngressRouter 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 리버스 프록시를 해줄 Ingress 리소스를 만들겠습니다. 더 세세한 설정이 필요한 경우 traefik 의 커스텀 리소스인 IngressRouter를 이용하셔도 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://cert-manager.io/docs/usage/ingress/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://cert-manager.io/docs/usage/ingress/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/services-networking/ingress/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kubernetes.io/docs/concepts/services-networking/ingress/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1686889561410&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: traefik
    cert-manager.io/cluster-issuer: &quot;letsencrypt-staging&quot;
  name: sftblw-moe-legacy-ingress
  namespace: sftblw-moe-legacy
spec:
  rules:
  - host: sftblw.moe
    http:
      paths:
      - pathType: Prefix
        path: &quot;/&quot;
        backend:
          service:
            name: sftblw-moe-legacy
            port:
              number: 80
  tls:
  - hosts:
    - sftblw.moe
    secretName: tls-sftblw-moe-staging # &amp;lt; cert-manager will store the created certificate in this secret. 임의로 정해도 상관없나보네요&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;포트포워딩을 바꾸고 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 http/https 포트포워딩을 MetalLB가 할당한 IP로 바꿔서 잘 돌아가나 확인해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZQfWY/btsj8inmztR/8FakT4xgk4X2xkbVuXdln1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZQfWY/btsj8inmztR/8FakT4xgk4X2xkbVuXdln1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZQfWY/btsj8inmztR/8FakT4xgk4X2xkbVuXdln1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZQfWY%2Fbtsj8inmztR%2F8FakT4xgk4X2xkbVuXdln1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;572&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BPngQ/btsj6QEV30i/fQrDkf3v9GZIb7iStYtFD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BPngQ/btsj6QEV30i/fQrDkf3v9GZIb7iStYtFD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BPngQ/btsj6QEV30i/fQrDkf3v9GZIb7iStYtFD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBPngQ%2Fbtsj6QEV30i%2FfQrDkf3v9GZIb7iStYtFD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1083&quot; height=&quot;834&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XH9vk/btskdWDUTJw/pZerGLpZ9hzYMfPareLlaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XH9vk/btskdWDUTJw/pZerGLpZ9hzYMfPareLlaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XH9vk/btskdWDUTJw/pZerGLpZ9hzYMfPareLlaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXH9vk%2FbtskdWDUTJw%2FpZerGLpZ9hzYMfPareLlaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;913&quot; height=&quot;954&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 되네요. 이제 프로덕션으로 바꾸면 되겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1686890118874&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: mymail@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - http01:
        ingress:
          class: traefik&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686890133671&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: traefik
    cert-manager.io/cluster-issuer: &quot;letsencrypt-prod&quot;  # prod로 변경
  name: sftblw-moe-legacy-ingress
  namespace: sftblw-moe-legacy
spec:
  rules:
  - host: sftblw.moe
    http:
      paths:
      - pathType: Prefix
        path: &quot;/&quot;
        backend:
          service:
            name: sftblw-moe-legacy
            port:
              number: 80
  tls:
  - hosts:
    - sftblw.moe
    secretName: tls-sftblw-moe-prod&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-ke-size=&quot;size16&quot;&gt;Longhorn&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://longhorn.io/docs/1.5.0/deploy/install/#installation-requirements&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://longhorn.io/docs/1.5.0/deploy/install/#installation-requirements&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1689565564635&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.5.0/deploy/prerequisite/longhorn-iscsi-installation.yaml
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.5.0/deploy/prerequisite/longhorn-nfs-installation.yaml

curl -sSfL https://raw.githubusercontent.com/longhorn/longhorn/v1.5.0/scripts/environment_check.sh | bash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://longhorn.io/docs/1.5.0/deploy/install/install-with-helm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://longhorn.io/docs/1.5.0/deploy/install/install-with-helm/&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1689565619822&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm repo add longhorn https://charts.longhorn.io
helm repo update

helm upgrade --install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace --version 1.5.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/longhorn/longhorn/blob/master/examples/storageclass.yaml&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/longhorn/longhorn/blob/master/examples/storageclass.yaml&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl -f 아래_파일.yml&lt;/p&gt;
&lt;pre id=&quot;code_1689565727619&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: longhorn-delete-single
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: &quot;1&quot;
  staleReplicaTimeout: &quot;2880&quot;
  fromBackup: &quot;&quot;
  fsType: &quot;ext4&quot;
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: longhorn-delete-double
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: &quot;2&quot;
  staleReplicaTimeout: &quot;2880&quot;
  fromBackup: &quot;&quot;
  fsType: &quot;ext4&quot;
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: longhorn-delete-triple
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: &quot;3&quot;
  staleReplicaTimeout: &quot;2880&quot;
  fromBackup: &quot;&quot;
  fsType: &quot;ext4&quot;
  kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: longhorn-retain-single
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Retain
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: &quot;1&quot;
  staleReplicaTimeout: &quot;2880&quot;
  fromBackup: &quot;&quot;
  fsType: &quot;ext4&quot;
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: longhorn-retain-double
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Retain
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: &quot;2&quot;
  staleReplicaTimeout: &quot;2880&quot;
  fromBackup: &quot;&quot;
  fsType: &quot;ext4&quot;
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: longhorn-retain-triple
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Retain
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: &quot;3&quot;
  staleReplicaTimeout: &quot;2880&quot;
  fromBackup: &quot;&quot;
  fsType: &quot;ext4&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl proxy 후 아래 사이트에 접속, 설정 계속 진행&lt;/p&gt;
&lt;pre id=&quot;code_1689565920360&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://127.0.0.1:8001/api/v1/namespaces/longhorn-system/services/http:longhorn-frontend:http/proxy/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TODO&lt;/p&gt;</description>
      <category>언어 무관/리눅스, 시스어드민</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/122</guid>
      <comments>https://sftblw.tistory.com/122#entry122comment</comments>
      <pubDate>Wed, 31 May 2023 11:37:16 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스프링 JDK 버전 호환</title>
      <link>https://sftblw.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에 낚여서 씀&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvcMz/btr3psmNZ76/RZJM9e3quES5B6NpBnsX6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvcMz/btr3psmNZ76/RZJM9e3quES5B6NpBnsX6k/img.png&quot; data-alt=&quot;잘못된 정보&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvcMz/btr3psmNZ76/RZJM9e3quES5B6NpBnsX6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmvcMz%2Fbtr3psmNZ76%2FRZJM9e3quES5B6NpBnsX6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;152&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;잘못된 정보&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.7.9/reference/html/getting-started.html#getting-started&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.spring.io/spring-boot/docs/2.7.9/reference/html/getting-started.html#getting-started&lt;/a&gt;&lt;/span&gt;
&lt;h2 id=&quot;getting-started.system-requirements&quot; data-ke-size=&quot;size26&quot;&gt;2. System Requirements&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 2.7.9 requires &lt;a href=&quot;https://www.java.com&quot;&gt;Java 8&lt;/a&gt; and is compatible up to and including Java 19. &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/5.3.25/reference/html/&quot;&gt;Spring Framework 5.3.25&lt;/a&gt; or above is also required.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Explicit build support is provided for the following build tools:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부트 2.7 스프링 8 지원하자나요 ㅠㅠ 댓글까지 막아놓고 이게 뭔 낚시래...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 H2 는 문제가 있음. 2.7 릴리즈 노트에 따르면, JOOQ 의 오픈소스 에디션이 Java 11 이상으로 올라가는 모양. 구매로 대처가 가능하다고. &lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes&lt;/a&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>특정 언어 관련/Java</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/121</guid>
      <comments>https://sftblw.tistory.com/121#entry121comment</comments>
      <pubDate>Mon, 13 Mar 2023 18:39:14 +0900</pubDate>
    </item>
    <item>
      <title>tuple struct 는 동시에 함수이기도 하다</title>
      <link>https://sftblw.tistory.com/120</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://iced.rs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;iced 프레임워크&lt;/a&gt;의 예시 중 이벤트를 메시지로 매핑하는 부분을 따라하고 있었음&lt;/li&gt;
&lt;li&gt;패러미터는 함수를 받는데, 예시 코드에서는 enum variant 를 익명함수가 들어갈 곳에 그냥 넣어놨음&lt;br /&gt;-&amp;gt; 이 시점에서 &quot;뭐지&quot; 그러고   가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1407&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C7r3u/btrXm6oc7t8/MD4YQJuo3G7hSQqxXDEGb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C7r3u/btrXm6oc7t8/MD4YQJuo3G7hSQqxXDEGb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C7r3u/btrXm6oc7t8/MD4YQJuo3G7hSQqxXDEGb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC7r3u%2FbtrXm6oc7t8%2FMD4YQJuo3G7hSQqxXDEGb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1407&quot; height=&quot;746&quot; data-origin-width=&quot;1407&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rqxkT/btrXim0ROKD/VHFflCu55DZDIk07P3NSJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rqxkT/btrXim0ROKD/VHFflCu55DZDIk07P3NSJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rqxkT/btrXim0ROKD/VHFflCu55DZDIk07P3NSJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrqxkT%2FbtrXim0ROKD%2FVHFflCu55DZDIk07P3NSJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;351&quot; height=&quot;181&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;181&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업하던 코드. InputRegex(String) 은 분명 그냥 enum variant 인데, 함수가 들어가야 할 곳에 들어감.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1189&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m3VNK/btrXlick2F2/Nx89IXoFuhNqI1rOqkYHhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m3VNK/btrXlick2F2/Nx89IXoFuhNqI1rOqkYHhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m3VNK/btrXlick2F2/Nx89IXoFuhNqI1rOqkYHhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm3VNK%2FbtrXlick2F2%2FNx89IXoFuhNqI1rOqkYHhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1189&quot; height=&quot;247&quot; data-origin-width=&quot;1189&quot; data-origin-height=&quot;247&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알고보니 tuple struct 는 동명의 함수도 정의된다더라 (삐빅 정상이었습니다)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/rust/comments/firk1r/tuple_structs_are_functions/&quot;&gt;reddit.com/r/rust/comments/firk1r/tuple_structs_are_functions/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/58736827/is-it-possible-to-pass-a-tuple-struct-constructor-to-a-function-to-return-differ&quot;&gt;https://stackoverflow.com/questions/58736827/is-it-possible-to-pass-a-tuple-struct-constructor-to-a-function-to-return-differ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/74824290/why-do-tuple-structs-and-enum-variants-behave-like-functions&quot;&gt;https://stackoverflow.com/questions/74824290/why-do-tuple-structs-and-enum-variants-behave-like-functions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://don.naru.cafe/@sftblw/109750747341319743&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://don.naru.cafe/@sftblw/109750747341319743&lt;/a&gt;&lt;/p&gt;</description>
      <category>특정 언어 관련/Rust</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/120</guid>
      <comments>https://sftblw.tistory.com/120#entry120comment</comments>
      <pubDate>Fri, 27 Jan 2023 15:24:18 +0900</pubDate>
    </item>
    <item>
      <title>[웹 API 디자인 2장] API 디자인에도 육하원칙이 있다</title>
      <link>https://sftblw.tistory.com/119</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=254607285&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=254607285&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 1장과 2장을 마저 해치웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서점 갔다가 홀린듯이 사버린 책 중 하나인데, 뭔가를 만들어보려면 어느 지점에서 꼭 막히더란 말이죠. 그런 상황에서 도움이 될 것 같다고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각설하고.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2장 - 사용자를 위한 API 디자인하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1장의 주요 내용은 API 디자인이 왜 중요한지이고, 2장에서는 그걸 어떻게 하는지의 개요를 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1장에서 그지같은 디자인의 (그냥 봐서는 뭐 하는 물건인지도 알 수 없는) 범용 원격 드론 조종장치로 예시를 들고, 2장은 보다 쉬운 전자레인지로 예시를 듭니다. 예시가 중요한 건 아니고 거기에 담긴 게 중요한데... 2장 목차부터 정리하죠.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2.1 일상 속 사용자 인터페이스를 디자인하는 올바른 관점&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2.1.1 작업 방식에 집중하면 인터페이스가 복잡해진다&lt;/li&gt;
&lt;li&gt;2.1.2 사용자가 할 수 있는 일에 집중하면 인터페이스는 단순해진다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2.2 소프트웨어 인터페이스 디자인 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2.2.1 API를 소프트웨어의 제어판처럼 바라보기&lt;/li&gt;
&lt;li&gt;2.2.2 컨슈머의 관점에 집중해 단순한 API를 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2.3 API의 목표 식별 과정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2.3.1 무엇을 어떻게 하는가&lt;/li&gt;
&lt;li&gt;2.3.2 어떤 걸 입력하고 어떤 게 출력되는가&lt;/li&gt;
&lt;li&gt;2.3.3 누락된 목표가 있는가&lt;/li&gt;
&lt;li&gt;2.3.4 모든 사용자를 찾아냈는가&lt;/li&gt;
&lt;li&gt;2.3.5 API 목표 캔버스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2.4 API 디자인에서 피해야 할 프로바이더 관점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2.4.1 데이터가 미치는 영향&lt;/li&gt;
&lt;li&gt;2.4.2 코드와 비즈니스 로직이 주는 영향&lt;/li&gt;
&lt;li&gt;2.4.3 소프트웨어 아키텍처에서 받는 영향&lt;/li&gt;
&lt;li&gt;2.4.4 인적 조직으로 인한 영향&lt;/li&gt;
&lt;li&gt;2.4.5 API 목표 캔버스에서 프로바이더 관점 찾기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내용은 책의 목차를 보고 그대로 타이핑한 게 맞습니다. 그렇지만 목차만큼 내용을 되새기기에 유용한 건 없죠.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 일상 속 사용자 인터페이스를 디자인하는 올바른 관점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자레인지의 예시가 여기에서 나옵니다. 전자레인지는 작업 방식에 집중한 나머지, 이름도 전자레인지가 아니었고 (소자의 이름인 &quot;마그네트론&quot;), 버튼을 누르고 있는 동안 동작한다는 괴악한 방식이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 원하는 건 그저 음식을 (무엇을) 데우는 (어떻게) 것 뿐이었는데...&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 소프트웨어 인터페이스 디자인 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마그네트론 2000 을 전자레인지로 바꾸는 과정을 API와 대응하여, API 도 똑같이 사용자 관점에서 = 사용자가 무엇을 어떻게 하고싶은지에 초점을 맞춰야 한다는 걸 설명합니다. 그러면 쓰기 편하고 단순한 게 완성되죠. 자세한 사항은 장막 밑에 숨기자구요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 API의 목표 식별 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 관점에서 무엇이 필요한지 단계적으로 살펴보면서, 어떻게 사용자 관점에서 필요한 API 를 찾아내는지 설명합니다. 그 결과가 &quot;API 목표 캔버스&quot; 라는 도구로 나타납니다. 이건 육하원칙과 굉장히 비슷합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 목표 캔버스의 6가지 칼럼
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;누가&lt;/li&gt;
&lt;li&gt;무엇을&lt;/li&gt;
&lt;li&gt;어떻게&lt;/li&gt;
&lt;li&gt;입력 + 입력의 원천&lt;/li&gt;
&lt;li&gt;출력 + 출력의 사용처&lt;/li&gt;
&lt;li&gt;목표 = 요약&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;API 목표 캔버스의 활용방법&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(입력의 원천, 출력의 사용처) 로부터 빠져있던 새로운 목표를 찾아냅니다.&lt;/li&gt;
&lt;li&gt;목표 = 요약 부분을 보고, 진짜로 이게 사용자한테 필요한 건지 다시 확인해봅니다 (2.4절)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거 보면서 생각한 게, DDD 에서 얘기하는 도메인 디자인하는 거랑 비슷하다는 생각이 들더라구요. 이 책을 보고 DDD쪽 책을 보면 좀 더 와닿지 않았을까 싶네요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.4 API 디자인에서 피해야 할 프로바이더 관점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API에 내부 사정을 묻히지 않기 위해, 내부 사정이 될 수 있는 게 무엇인지 알아봅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 ~= DB 구조&lt;/li&gt;
&lt;li&gt;코드 &amp;amp; 비즈니스 구조 ~= 내부 소스의 함수 목록&lt;/li&gt;
&lt;li&gt;소프트웨어 아키텍처 구조 ~= 너네 마이크로서비스 분리된 거가 API 에 반영될 필요까지는 없잖아&lt;/li&gt;
&lt;li&gt;인적 조직 ~= 너네 부서가 분리되어있는 걸 API 에서까지 알아야 해?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 명언 몇 가지가 소개되는데...&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;(어떤 형태이든) 시스템은 개발한 조직의 의사소통 구조를 닮아간다&quot;&lt;br /&gt;- 조직에서는 발명을 어떻게 하는가?, 1968년 4월, 멜 콘 웨이 (Mel Conway)&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여러분에게 보시다시피, 우리에게 필요했던 것은 마지막 질문 하나였습니다.&lt;br /&gt;&lt;b&gt;&quot;정말 컨슈머가 이런 걸 궁금해할까?&quot;&lt;/b&gt;&lt;br /&gt;이 질문 하나로, 조금이라도 컨슈머 관점 (데이터, 코드와 비즈니스 로직, 소프트웨어 아키텍처 또는 인적 조직) 을 지니고 있는지 확인할 수 있습니다.&lt;br /&gt;- 책 50페이지, 2.4절 마무리부분, 아노드 로렛 (Arnoud Louret)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분은 API 디자인 외에도 정말 광범위하게 적용되는 내용이 아닌가 싶습니다.&lt;/p&gt;</description>
      <category>책읽기</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/119</guid>
      <comments>https://sftblw.tistory.com/119#entry119comment</comments>
      <pubDate>Mon, 5 Dec 2022 21:46:08 +0900</pubDate>
    </item>
    <item>
      <title>[python] for 문의 변수도 로컬 변수다</title>
      <link>https://sftblw.tistory.com/118</link>
      <description>&lt;pre id=&quot;code_1663224913541&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def make_greeter():
    def nice_to_meet(someone):
        print(f'hello, {someone}!')

    greeter_list = []

    for name in ['길동', '둘리', '또치']:
        greeter_list.append(
            lambda: nice_to_meet(name)
        )
    
    return greeter_list

my_greeters = make_greeter()

for greeter_fn in my_greeters:
    greeter_fn()
    
# 결과:
# hello, 또치!
# hello, 또치!
# hello, 또치!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;627&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFDWje/btrMaFXj6nC/v1KyvVwkPIZN7ZU0d6Wvs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFDWje/btrMaFXj6nC/v1KyvVwkPIZN7ZU0d6Wvs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFDWje/btrMaFXj6nC/v1KyvVwkPIZN7ZU0d6Wvs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFDWje%2FbtrMaFXj6nC%2Fv1KyvVwkPIZN7ZU0d6Wvs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;627&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;627&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 이 스코핑 싫어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약간의 설명: 제 기억이 맞다면 python 은 제어문에 관계 없이 함수 단위로 스코프를 지정할 겁니다. for 문이어도 예외가 없고 그래서 name이 make_greeter() 의 지역변수가 되는 것... name에 마지막으로 할당된 값은 '또치' 이고, lambda에서 해당 지역변수를 참조하므로 또치밖에 안 나오는 거겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 스코프로 내려도 별반 다르지 않네요&lt;/p&gt;
&lt;pre id=&quot;code_1663225091872&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def nice_to_meet(someone):
    print(f'hello, {someone}!')

greeter_list = []

for name in ['길동', '둘리', '또치']:
    greeter_list.append(
        lambda: nice_to_meet(name)
    )

for greeter_fn in greeter_list:
    greeter_fn()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이런 글이 올라왔냐면... 이거에 당했거든요.&lt;/p&gt;</description>
      <category>특정 언어 관련/Python</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/118</guid>
      <comments>https://sftblw.tistory.com/118#entry118comment</comments>
      <pubDate>Thu, 15 Sep 2022 15:55:25 +0900</pubDate>
    </item>
    <item>
      <title>OCI OKE에 mastodon을 올린 기록</title>
      <link>https://sftblw.tistory.com/117</link>
      <description>&lt;pre id=&quot;code_1659321266203&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# network load balancer
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.service.annotations.&quot;oci\.oraclecloud\.com/load-balancer-type&quot;=&quot;nlb&quot; \
  --set controller.service.annotations.&quot;oci-network-load-balancer\.oraclecloud\.com/security-list-management-mode&quot;=&quot;All&quot; \
  --set controller.service.loadBalancerIP=&quot;지정받은.IP주소를.적어.주세요&quot; \
  --set controller.service.externalTrafficPolicy=Local&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오라클 클라우드는 무료 티어를 정말 넉넉하게 줍니다. 후발주자인 만큼 홍보 목적으로 많이 주는 거겠지 싶지만서도, ARM A1 인스턴스가 4oCPU 와 24GB 램이 무료라니 말이 됩니까. 거기다가 한국 리전도 두 군데나 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 마스토돈이라는 SNS(트위터 같은 마이크로블로깅)를 이용하고 있는데, 정확히는 워드프레스 같은 엔진의 개념에 가깝습니다. 어찌 되었든, 중요한 건 서버가 가끔 죽는다는 거죠. 마스토돈의 특성상 이메일처럼 동작하므로 다른 서버에 가서 &quot;서버 죽었어~&quot; 를 해도 되기는 하는데요, 기존에 쓰던 계정을 잠깐이나마 못 쓰게 된다는 건 아쉬운 일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 덕분에 &quot;안 죽는 서비스&quot;에 대한 로망이 생기고, 쿠버네티스 공부에 손을 대게 되었습니다. (힘든 길 힘든 길...) 오랜 시간이 지난 결과 (대략 몇 개월?) 결과적으로 &quot;쿠버네티스는 docker-compose 의 확장판이라고 생각하면 된다&quot; 까지 오게 되었고, 이윽고 서버를 올리는 데 성공했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게... 여러 가지 특수성이 겹쳐서 난이도가 꽤 올라갔었습니다. 그래서 그 과정을 정리해두고자 합니다. 전체 과정을 기록하진 않을거고, 트러블슈팅을 했던 내용만 기록하고자 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ARM64: 공식 차트를 쓸 수 없었던 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마스토돈 프로젝트에서는 helm 차트를 제공하고 있습니다. 다만, 마스토돈은 ruby on rails 프로젝트이고 sidekiq (job queue) 을 그대로 쓰고있어서 redis (sidekiq 백엔드) 가 추가로 필요하며, 메인 DB인 postgresql 도 필요로 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 마스토돈 helm 차트에서는 redis 와 postgreql 의 의존성 차트를 Bitnami 것을 걸어두고 있습니다. 이 분들의 이미지가 참 좋아보이는데 문제는 &lt;a href=&quot;https://github.com/bitnami/charts/issues/7305&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ARM64를 아직 지원하지 않는다&lt;/a&gt;는 것입니다. (글 쓰는 시점에서 그렇다는 거지, 나중에는 지원이 들어올 수도 있을 것입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 ARM이냐... 성능에 비해 가격이 엄청 싸다고 알고있어서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 의존성이 걸려있는 두 helm 차트를 떼어내고, 차트 안에 공식 docker 이미지를 쓰는 postgresql 과 redis를 직접 넣기로 했습니다. 데이터를 영구히 유지해야 하는 앱에는 StatefulSet 이 적합하다고 하더라구요. pod과 PVC 등의 리소스 이름들이 일관된 형태로 유지되기 때문입니다. 그래서 일단 스케일링은 접어두고 단일 pod을 하나씩 띄우기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 삽질이 오래 걸렸는데, 두 가지 이유가 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;k8s의 개념만 대충 훑어보고 돌입했다가 문서 보는 방법을 잘 몰라서 모르는 부분을 찾아보지 못했기 때문입니다.&lt;/li&gt;
&lt;li&gt;helm 에서 쓰는 Go의 템플릿 문법을 여기에서 처음 봐서, 이게 뭐하는 건지 이해하는데 오래걸렸습니다.&lt;br /&gt;-&amp;gt; 셸에서 주로 쓰는 파이프 문법... 이라는 거만 인지하면 어렵지 않더라구요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;k8s 문서 잘 보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k8s 의 문서는 크게 두 부분으로 이루어져있다는 점을 명심해야 쉽게 이해하고 모르는 부분을 하나하나 찾아볼 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개념 설명 파트&lt;/li&gt;
&lt;li&gt;API 설명 파트 = YAML의 스키마 설명 파트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 설명이야 누구나 시작할 때 처음에 읽으실테고, 문제는 API 설명 파트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 설명 파트가 YAML의 스키마를 설명하는 부분인데 그걸 몰랐습니다. k8s의 모든 리소스는 kubernetes API 서버를 경유하여 JSON 형태로 주고받는데, 우리가 쓰는 YAML은 그 API 서버에 저장되어 있는 (아마 etcd?) JSON 을 보기 편하게 만든 것에 불과하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여튼, YAML의 각 요소가 궁금하시면 API 설명 파트를 참조하세요. 레퍼런스 섹션에 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;299&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFVQoX/btrIkoKmCos/tvQkKjv4k488QtWHoAXRTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFVQoX/btrIkoKmCos/tvQkKjv4k488QtWHoAXRTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFVQoX/btrIkoKmCos/tvQkKjv4k488QtWHoAXRTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFVQoX%2FbtrIkoKmCos%2FtvQkKjv4k488QtWHoAXRTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;299&quot; height=&quot;391&quot; data-origin-width=&quot;299&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오라클 클라우드 상에서의 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 클라우드 얘기를 해야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;되도록이면 한국에 각종 서비스를 구성하고, 가급적이면 Oracle Cloud Infrastructure (aka OCI) 서비스를 이용하여 이용가격을 낮추고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(여담인데, 집에서 쿠버네티스 환경을 구성하시려면 k3s + metalLB 를 설치하시거나 요즘에는 AWS에서 셀프 서비스 킷을 제공한다는 거 같더라구요. 그런 쪽을 이용하시면 될 거 같습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 DNS: Cloudflare에 수동으로 구성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OCI의 DNS가 며칠이 지나도록 한국에서 갱신이 안 되더라구요. 포기했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오브젝트 스토리지: OCI 의 오브젝트 스토리지를 사용했습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소규모 SNS 서비스의 대부분의 비용은 컴퓨팅과 오브젝트 스토리지에서 나가므로 적절한 안정성과 성능을 갖추면서도 싼 곳을 선택하는게 중요합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;wasabi 는 저번에 소셜망에서 여러 서버가 터져나간다는 소식을 들어서 배제했습니다.&lt;/li&gt;
&lt;li&gt;storj DCS는 작은 파일을 자잘자잘하게 올리기에는 너무 느립니다.&lt;/li&gt;
&lt;li&gt;AWS S3 괜찮긴 한데...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CDN: AWS Cloudfront를 사용했습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CDN 서비스는 아직 아직 오라클에 없습니다.&lt;/li&gt;
&lt;li&gt;Cloudflare는 Pay as you go 플랜이 없어서 최소 가격이 높아서 배제했습니다. 무료 플랜은 서울이나 대한민국이 아니죠.&lt;/li&gt;
&lt;li&gt;남는 건 AWS 등의 클라우드 벤더인데, 일본어로 된 OCI 오브젝트 스토리지 &amp;lt;-&amp;gt; AWS Cloudfront 연결 가이드가 있어서 Cloudfront를 선택했습니다. (계정이 이미 있기도 하구요)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;K8S 서비스는 직접 설치하지 않는다면 오라클이 관리하는 OKE 외에는 선택지가 없습니다.&lt;/li&gt;
&lt;li&gt;SMTP도 오라클 OCI 를 사용했는데 어떻게 설정했는지 잘 기억나지 않네요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ElasticSearch 의 설치도 미뤘습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OCI 오브젝트 스토리지의 S3 호환 API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 방식으로는 버킷 접근이 안 되고 반드시 서브경로식으로 접근해야 한다는 건 많이들 아실테고요... 저는 이 점에 대해서는 기도 메타였습니다. (마스토돈이 지원해줘야 하니; 근데 다행히도 되던...)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;S3 호환 API 키 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 호환 오브젝트 스토리지 설정에서 가장 애 먹는 부분이 API 키를 찾는 것입니다. 그 이유는 오라클이 OCI의 해당 부분에서 &quot;S3&quot;라는 이름을 빼버렸기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위치도 이상한 데에 있습니다. 오브젝트 스토리지나 버킷 섹션에 없고, 내 프로파일 -&amp;gt; 고객 암호 키(Customer Secret) 에 있습니다. 한글이니 더 못알아먹었습니다 (ㅋㅋㅋㅋ ㅠㅠ)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wxb0l/btrIhiScDjN/KDAMwFjEHtNvONF9mQOSS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wxb0l/btrIhiScDjN/KDAMwFjEHtNvONF9mQOSS1/img.png&quot; data-origin-width=&quot;468&quot; data-origin-height=&quot;276&quot; data-is-animation=&quot;false&quot; style=&quot;width: 38.3393%; margin-right: 10px;&quot; data-widthpercent=&quot;38.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wxb0l/btrIhiScDjN/KDAMwFjEHtNvONF9mQOSS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWxb0l%2FbtrIhiScDjN%2FKDAMwFjEHtNvONF9mQOSS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;468&quot; height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z5vY6/btrIdYNHenE/tD4VHSefT13NHxA0kyZZG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z5vY6/btrIdYNHenE/tD4VHSefT13NHxA0kyZZG1/img.png&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;333&quot; data-is-animation=&quot;false&quot; style=&quot;width: 60.4979%;&quot; data-widthpercent=&quot;61.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z5vY6/btrIdYNHenE/tD4VHSefT13NHxA0kyZZG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz5vY6%2FbtrIdYNHenE%2FtD4VHSefT13NHxA0kyZZG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;891&quot; height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아, 마스토돈은 엔드포인트에 https 를 붙여줘야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OCI 버킷과 AWS Cloudfront 연결하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두세 가지 방식이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버킷을 퍼블릭으로 만들기 - 제가 봤던 &lt;a href=&quot;https://qiita.com/shirok/items/050fa7dc963c5a5a6a75&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;일본어 가이드&lt;/a&gt;에서 설명&lt;/li&gt;
&lt;li&gt;API 키로 접근하게 만들기 - 번거로워 보이고 할 줄도 몰라서 시도조차 안 해봄 (추가 컴퓨팅 비용도 들 듯)&lt;/li&gt;
&lt;li&gt;&lt;u&gt;사전 인증된 요청 URL 사용하기&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 일본어 가이드를 따라서 버킷을 퍼블릭으로 만들었다가, 사전 인증된 요청 URL 방식으로 바꿨습니다. (비공개로 바꾸고요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;사전 인증된 요청&lt;/u&gt;이라는 방식은 좀 바보같아보이는 방법인데, 조회용 URL 자체가 비밀키가 되는 방법입니다. https니까 딱히 상관 없으려나? 거기다 날짜 제한을 반드시 걸어야 하는데, 오라클 포럼 어딘가에서도 그냥 먼 미래로 걸라고 하더라구요. 버킷 자체가 인터넷에 공개되는 거보다는 나으니까...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;917&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt1OOW/btrIfSGdFo0/8MWODLFP8HjVnA1TVLZHfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt1OOW/btrIfSGdFo0/8MWODLFP8HjVnA1TVLZHfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt1OOW/btrIfSGdFo0/8MWODLFP8HjVnA1TVLZHfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt1OOW%2FbtrIfSGdFo0%2F8MWODLFP8HjVnA1TVLZHfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;917&quot; height=&quot;511&quot; data-origin-width=&quot;917&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Cloudfront 쪽에서는 해당 URL을 엔드포인트로 잡고 설정해두면 됩니다. Cloudfront 를 사용하는 경우 해당 주소의 SSL (TLS 인증서) 발급 및 자동 재발급도 무료이므로 AWS에서 SSL을 설정하시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XIdsb/btrIhjXPVAD/UphJk5fOXUyRPEjTH6IVRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XIdsb/btrIhjXPVAD/UphJk5fOXUyRPEjTH6IVRK/img.png&quot; data-alt=&quot;주소 자체가 비밀이긴 한데, 따라하시려면 어느 부분까지 넣어야 하는지는 알려드려야 해서 일부는 모자이크하지 않았습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XIdsb/btrIhjXPVAD/UphJk5fOXUyRPEjTH6IVRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXIdsb%2FbtrIhjXPVAD%2FUphJk5fOXUyRPEjTH6IVRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;928&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;928&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;주소 자체가 비밀이긴 한데, 따라하시려면 어느 부분까지 넣어야 하는지는 알려드려야 해서 일부는 모자이크하지 않았습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OCI 클라우드 셸&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5GB 공간이 있는 웹콘솔을 무료로 줍니다. 계정이 남아있는 한 데이터가 삭제되지는 않는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곳곳에 OCI Cloud Shell 을 여는 버튼이 숨어있습니다. K8S 설정도 거기서 시키는 대로 하면 금방 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 자동화된 CD를 구축하는 건 미뤄뒀으므로, 명령줄로 직접 helm install 을 치는 방향으로 진행했는데, 웹 환경의 셸을 이용했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ev9uY/btrIkqnUpFI/b4od3daPIcy3LA1dLZKuyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ev9uY/btrIkqnUpFI/b4od3daPIcy3LA1dLZKuyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ev9uY/btrIkqnUpFI/b4od3daPIcy3LA1dLZKuyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEv9uY%2FbtrIkqnUpFI%2Fb4od3daPIcy3LA1dLZKuyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;953&quot; height=&quot;122&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안전하지 않을 수도 있겠지만, 일단 private 값들은 비공개 gitlab 저장소에 구성했습니다. 요즘엔 암호화해서 차트랑 같이 둔다던데 이미 이렇게 구성해놔서...&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SMTP 서비스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자메일 섹션에서 이런저런 번거로운 작업을 해주시면 설정이 됩니다. 사용하실 DNS 쪽과 같이 설정해주셔야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 추가&lt;/li&gt;
&lt;li&gt;DKIM 설정&lt;/li&gt;
&lt;li&gt;SPF 설정&lt;/li&gt;
&lt;li&gt;승인된 발신자 설정 (승인된 발신자가 아니면 발신이 안 됨)&lt;/li&gt;
&lt;li&gt;그 외에는 밑의 mastodon 설정 yaml 에 적어놓은 걸 참고해주세요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오라클 SMTP 발신 서비스의 장점은 mailgun이나 sparkpost 같은 서비스와는 다르게 최소 비용이 없다는 거겠습니다. 대신 사용량이 좀 많아지면 오라클 쪽이 더 비싸지는 지점이 있더라구요. 취사선택하십셔&lt;/p&gt;
&lt;pre id=&quot;code_1658839115544&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  smtp:
    auth_method: plain
    delivery_method: smtp
    enable_starttls_auto: true
    from_address: 이름 &amp;lt;전자메일_전송_@승인된_발신자&amp;gt;
    openssl_verify_mode: none
    login: &quot;내 프로파일 &amp;gt; SMTP 인증서 &amp;gt; 사용자 이름&quot;
    password: &quot;내 프로파일 &amp;gt; SMTP 인증서 &amp;gt; 사용자 이름 (만들었을 때 한 번 나온 암호)&quot;
    port: 587
    server: &quot;전자메일 전송 &amp;gt; 구성 &amp;gt; 공용 끝점&quot;
    tls: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 이런 설정으로 동작이 됐는데 맞는 설정인지는 모르겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OKE K8S 클러스터 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개되는 템플릿쪽으로 만듭니다. 비공개면 클라우드 셸에서도 접속이 안 됐던걸로... 정확하게는 모릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A1 암페어 ARM 인스턴스 / 2oCPU 12GB램 2개 노드풀로 구성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;다만, 요금 문제가 있습니다. 어째 500원정도 나오길래 보니까 로드밸런서가 100MB 고정으로 만들어져있었습니다. 프리티어는 플렉서블 / 최소 10MB 최대 10MB 까지인데, 기본 템플릿이 거기에 맞게 고쳐지지 않았나 보네요. 시키는 대로 바꿔주시면 됩니다.&lt;/s&gt; 하단 추가&lt;s&gt;&lt;br /&gt;&lt;/s&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ingress-nginx 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 요청을 받아야 하니까 ingress-nginx 를 설치해야 합니다. ingress-nginx를 설치하면 LoadBalancer 타입의 Service도 같이 생기고, 거기에 맞는 로드밸런서&lt;i&gt;(OCI 리소스)&lt;/i&gt;를 OCI 에서 자동으로 만들어주는데, 위에 취소선 그은 상황이 발생하지 않으려면 설치할 때 설정을 좀 해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 공용 IP를 예약합시다. 네트워킹 &amp;gt; IP 관리 &amp;gt; 예약된 공용 IP. 공용 IP를 예약해두지 않으면 자동으로 할당해주는데, 언제 바뀔지 누가 알겠습니까. Oracle의 IP 풀에서 하나를 빌리도록 합시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAvLE5/btrIkpYahQE/xkwZj3pGQzwiEq5B9WD15k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAvLE5/btrIkpYahQE/xkwZj3pGQzwiEq5B9WD15k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAvLE5/btrIkpYahQE/xkwZj3pGQzwiEq5B9WD15k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAvLE5%2FbtrIkpYahQE%2FxkwZj3pGQzwiEq5B9WD15k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1160&quot; height=&quot;400&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 helm 으로 설치하면 됩니다. 옵션은 읽어보시고 바꾸시되, loadBalancerIP 를 위에서 예약받은 IP로 지정해야 저기에 묶입니다. 그 외 옵션은 생성되는 로드밸런서가 flexible 이고 무료 제한인 10MB ~ 10MB 안에서 생성되게 하기 위한 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1658926443629&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.service.annotations.&quot;service\.beta\.kubernetes\.io/oci-load-balancer-shape&quot;=&quot;flexible&quot; \
  --set controller.service.annotations.&quot;service\.beta\.kubernetes\.io/oci-load-balancer-shape-flex-min&quot;=10  \
  --set controller.service.annotations.&quot;service\.beta\.kubernetes\.io/oci-load-balancer-shape-flex-max&quot;=10 \
  --set controller.service.loadBalancerIP=&quot;지정받은.IP주소를.적어.주세요&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속도제한을 피하려면 L7/L4 로드밸런서가 아닌 L4/L3 네트워크 로드밸런서를 사용하시면 됩니다. 옵션은 문서를 읽어보시구요.&lt;/p&gt;
&lt;pre id=&quot;code_1659321275413&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# network load balancer
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.service.annotations.&quot;oci\.oraclecloud\.com/load-balancer-type&quot;=&quot;nlb&quot; \
  --set controller.service.annotations.&quot;oci-network-load-balancer\.oraclecloud\.com/security-list-management-mode&quot;=&quot;All&quot; \
  --set controller.service.loadBalancerIP=&quot;지정받은.IP주소를.적어.주세요&quot; \
  --set controller.service.externalTrafficPolicy=Local&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;helm 으로 설치하지 않으면 나중에 업데이트 할 때 머리가 아프지 않을까 싶습니다. 저의 경우 업데이트가 안 되는 이유는 OCI 인터페이스에서 로드밸런서 타입을 아예 바꿔버린 거긴 했는데요... 가급적이면 설정은 설치할 때 패러미터로 넘겨주고 거기서 바꾸는 게 좋을 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 문서&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;color: #969896;&quot;&gt;#&amp;nbsp;&lt;a href=&quot;https://developer.oracle.com/tutorials/flexible-load-balancers-oke/&quot;&gt;https://developer.oracle.com/tutorials/flexible-load-balancers-oke/&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #969896;&quot;&gt;#&amp;nbsp;&lt;a href=&quot;https://docs.oracle.com/en-us/iaas/Content/ContEng/Tasks/contengcreatingloadbalancer.htm#contengcreatingloadbalancer_topic_Specifying_Load_Balancer_Reserved_IP&quot;&gt;https://docs.oracle.com/en-us/iaas/Content/ContEng/Tasks/contengcreatingloadbalancer.htm#contengcreatingloadbalancer_topic_Specifying_Load_Balancer_Reserved_IP&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #969896;&quot;&gt;#&amp;nbsp;&lt;a href=&quot;https://github.com/oracle/oci-cloud-controller-manager/blob/master/docs/load-balancer-annotations.md&quot;&gt;https://github.com/oracle/oci-cloud-controller-manager/blob/master/docs/load-balancer-annotations.md&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;letsencrypt SSL 붙이기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cert-manager 를 사용합니다. &lt;s&gt;설치 yaml 이나&lt;/s&gt; &lt;a href=&quot;https://cert-manager.io/docs/installation/helm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;helm 등으로 구성요소들을 설치&lt;/a&gt;한 뒤, ClusterIssuer를 지정해서 넣으면 cert-manager 가 대응하는 ingress를 찾아서 알아서 인증서를 발급해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아맞아 이거 하시려면 mastodon 차트 외에도 따로 nginx ingress 리소스도 이미 별도로 설치하셨어야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1658838113922&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt # 이 부분이 일치해야 함
  labels:
    이런저런 레이블들
  name: 리소스이름
spec:
  rules:
  - host: 도메인
    http:
      paths:
      - backend:
          service:
            name: 대상-서비스
            port:
              number: 대상-포트
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - 도메인
    secretName: 도메인-tls # 시크릿 이름&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1658837795854&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: cert-manager.io/v1
kind: ClusterIssuer # Issuer 는 해당 namespace 전용입니다. 번거로우시죠? ClusterIssuer 가즈아
metadata:
  name: letsencrypt
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: 적당히 이메일 적어주세요
    # Name of a secret used to store the ACME account private key
    # 이 이름의 secret 에 키가 들어간대요
    privateKeySecretRef:
      name: letsencrypt (ingress에 적어놓은 거랑 같은 거)
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: nginx&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스토리지 리소스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;postgresql 과 redis 의 영속성 저장공간을 확보해야 하니 스토리지를 어디에 마련할지도 중요하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도로 스토리지 리소스를 만들고 싶지는 않은데, 자동 확장을 허용하고 싶으시다면 자동 확장이 기본으로 ON으로 되어있는 &quot;oci-bv&quot; 스토리지 클래스를 사용하시면 됩니다. &quot;oci&quot;가 더 낡은 포맷이라고 하더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 2개까지는 무료인 거 같아서...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 문서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en-us/iaas/Content/ContEng/Tasks/contengcreatingpersistentvolumeclaim.htm#contengcreatingpersistentvolumeclaim_topic_Provisioning_PVCs_on_BV_PV_Volume_expansion&quot;&gt;https://docs.oracle.com/en-us/iaas/Content/ContEng/Tasks/contengcreatingpersistentvolumeclaim.htm#contengcreatingpersistentvolumeclaim_topic_Provisioning_PVCs_on_BV_PV_Volume_expansion&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외의 차트 관련해서 삽질한 건 굳이 설명 드릴 필요가 있나 싶구요 (하도 이리저리 삽질한 게 많아서...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 차트가 AGPL 이었어서 공개해두긴 했습니다. &lt;a href=&quot;https://gitlab.com/naru-cafe/don-chart&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gitlab.com/naru-cafe/don-chart&lt;/a&gt; 쓰시라고 공개해둔 것은 아니므로, bitnami 쪽에서 ARM64 이미지를 공개해줄 때까지 기다렸다가 그걸 이용하시거나 그냥 공식 차트로 x64 인스턴스를 올리시는 걸 추천드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아, `watch -n 3 -d kubectl get all` 유용합니다. OCI 탭 하나 더 띄우고 실행해놓으시면 됩니다. 근데 나중에 보니까 -w 옵션이 따로 있는 거 같더라구요. get all에는 그냥은 또 안 되는...&lt;/p&gt;
&lt;h1&gt;TODO&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl exec 같은 걸로 pod에 직접 접근이 되는지 확인 (이런 게 있나...?)&lt;/li&gt;
&lt;li&gt;service account 가 뭔지 배우고 어떻게 설정되어있는지 보기&lt;/li&gt;
&lt;li&gt;helm install 이나 helm upgrade --install 로 설치한다고 해서 자동으로 롤링 릴리즈를 해주는 게 아니더라구요.&lt;/li&gt;
&lt;li&gt;CD 붙이기, elasticsearch 붙이기&lt;/li&gt;
&lt;li&gt;auto scailing&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>언어 무관/리눅스, 시스어드민</category>
      <category>K8s</category>
      <category>Kubernetes</category>
      <category>oci</category>
      <category>Oracle Cloud Infrastructure</category>
      <category>오라클</category>
      <category>오라클 클라우드</category>
      <category>오라클 클라우드 인프라</category>
      <category>쿠버네티스</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/117</guid>
      <comments>https://sftblw.tistory.com/117#entry117comment</comments>
      <pubDate>Tue, 26 Jul 2022 21:43:38 +0900</pubDate>
    </item>
    <item>
      <title>Array(n) 은 왜 map이 안 돼?</title>
      <link>https://sftblw.tistory.com/116</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 채팅방에서:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sftbrz: 2022.07.08.&lt;br /&gt;JS 고수님드라 Array(10).map(() =&amp;gt; []) 가 안 되는 이유 설명좀&lt;br /&gt;일단 하고자 하는 건 빈 배열의 배열을 만드는건데 방법은 &lt;a href=&quot;https://stackoverflow.com/a/49201210/4394750&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/a/49201210/4394750&lt;/a&gt; 에 있음&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kluid.so:&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;unknown.png&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n4KJJ/btrHapKHYcz/PB2AhCumZibKk8tpU11bS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n4KJJ/btrHapKHYcz/PB2AhCumZibKk8tpU11bS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n4KJJ/btrHapKHYcz/PB2AhCumZibKk8tpU11bS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn4KJJ%2FbtrHapKHYcz%2FPB2AhCumZibKk8tpU11bS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1228&quot; height=&quot;324&quot; data-filename=&quot;unknown.png&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;키가 없어서&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;doghood: 2022.07.08&lt;br /&gt;빈 배열 만드는 법은 알아ㅋ&lt;br /&gt;
&lt;pre id=&quot;code_1657639212392&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Array.from({length:10}).map((_, idx) =&amp;gt; idx);
Array(10) [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map 빼면 빈 배열이야ㅋ&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sftbrz:&lt;br /&gt;ES 체신버전 스펙임?&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kluid.so:&lt;br /&gt;2021일걸&lt;br /&gt;es22는 아직 제대로 안 봄&lt;br /&gt;ㅋㅋ&lt;br /&gt;&lt;a href=&quot;https://wormwlrm.github.io/2022/07/06/ECMAScript-2022.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://wormwlrm.github.io/2022/07/06/ECMAScript-2022.html&lt;/a&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sftbrz:&lt;br /&gt;es5 이후로는 안 읽어봐서 왠지 싹 바뀐거같이 생김&lt;br /&gt;&lt;a href=&quot;https://es5.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://es5.github.io/&lt;/a&gt; 가 체고였는데&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...생략...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1291&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEvPIF/btrHaW9lgol/WNNAK1mKFKj8FuoCE8Wjj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEvPIF/btrHaW9lgol/WNNAK1mKFKj8FuoCE8Wjj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEvPIF/btrHaW9lgol/WNNAK1mKFKj8FuoCE8Wjj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEvPIF%2FbtrHaW9lgol%2FWNNAK1mKFKj8FuoCE8Wjj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1291&quot; height=&quot;92&quot; data-origin-width=&quot;1291&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sftbrz:&lt;br /&gt;[,].map(it =&amp;gt; console.log(123))&lt;br /&gt;&amp;lt;- 출력결과 없고 배열만 나옴&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sftbrz:&lt;br /&gt;[,].length&lt;br /&gt;&amp;lt;- 1&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k6pPX/btrG4VxPdIZ/bK9CjkMh6wWyQfatUINYl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k6pPX/btrG4VxPdIZ/bK9CjkMh6wWyQfatUINYl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k6pPX/btrG4VxPdIZ/bK9CjkMh6wWyQfatUINYl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk6pPX%2FbtrG4VxPdIZ%2FbK9CjkMh6wWyQfatUINYl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1298&quot; height=&quot;124&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sftbrz:&lt;br /&gt;그렇네 프로퍼티(=키) 가 없어서네&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;194&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2ethf/btrG8RgPent/JiCZVORkcNqkgL42t1z3Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2ethf/btrG8RgPent/JiCZVORkcNqkgL42t1z3Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2ethf/btrG8RgPent/JiCZVORkcNqkgL42t1z3Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2ethf%2FbtrG8RgPent%2FJiCZVORkcNqkgL42t1z3Q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;194&quot; height=&quot;378&quot; data-origin-width=&quot;194&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sftbrz:&lt;br /&gt;해시코드에 물어보거나 블로그에 정리하기 딱인 거 같다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상한 언어&lt;/p&gt;</description>
      <category>특정 언어 관련/JavaScript</category>
      <author>Ch.</author>
      <guid isPermaLink="true">https://sftblw.tistory.com/116</guid>
      <comments>https://sftblw.tistory.com/116#entry116comment</comments>
      <pubDate>Wed, 13 Jul 2022 00:26:35 +0900</pubDate>
    </item>
  </channel>
</rss>