Jekyll2020-04-13T05:10:38+00:00https://kangwoo.github.io/feed.xmlI am the rainI can't be contained강우kangwoo@gmail.comGitOps and Kubernetes2019-12-18T11:20:37+00:002019-12-18T11:20:37+00:00https://kangwoo.github.io/devops/kubernetes/gitops-and-kubernetes<h1 id="gitops-스타일의-지속적-배포">GitOps 스타일의 지속적 배포</h1>
<p>쿠버네티스에 애플리케이션을 배포하는 방법은 여러가지가 있습니다.</p>
<p>가장 간단한 방법으로는, 사람이 직접 <code class="language-plaintext highlighter-rouge">kubectl</code> 를 실행해서 매니페스트를 클러스터에 적용하는 것입니다. 물론 사람이 실행하기 때문에 번거롭게, 실수가 쉽게 발생한다는 문제가 있습니다. 그래서 보통은 자동화 도구를 사용합니다. <code class="language-plaintext highlighter-rouge">Spinnaker</code>, <code class="language-plaintext highlighter-rouge">Jenkins X</code>, <code class="language-plaintext highlighter-rouge">Tekton</code>, <code class="language-plaintext highlighter-rouge">Argo Workflow</code>, <code class="language-plaintext highlighter-rouge">Argo CD</code> 와 같은 CI/CD 도구들을 사용해서 배포하는 것입니다.</p>
<p>이 문서에서는 요즘 사용되고 있는 GitOps 스타일의 방법을 사용하여, CI/CD 파이프라인을 만드는 방법에 대해서 설명하겠습니다.</p>
<p>블루/그린 배포, 카나리아 분석, 멀티 클라우드 배포 등의 고급 기능을 사용하려면, <code class="language-plaintext highlighter-rouge">Spinnaker</code>가 더 좋은 선택지가 될 수 있지만, 간단히 사용하기에 <code class="language-plaintext highlighter-rouge">Argo CD</code> 로도 충분하다고 생각합니다. 이 문서에는 <code class="language-plaintext highlighter-rouge">Argo CD</code> 를 사용합니다.</p>
<h1 id="gitops">GitOps</h1>
<p>GitOps 라는 용어는 <a href="https://www.weave.works/blog/gitops-operations-by-pull-request">Weaveworks</a>에서 만들었습니다.</p>
<p>GitOps의 핵심은 Git 저장소에 저장된 쿠버네티스 매니페스트 같은 파일을 이용하여, 배포를 선언적으로 한다는 것입니다. 즉, Git에 저장된 매니페스트가 쿠버네티스 클러스터에도 똑같이 반영된다는 것입니다.</p>
<p>이러한 방법은 이해하기 쉬운 운영 모델을 제공하며, Git을 사용하기 때문에 보안 및 감사 기능도 기본으로 제공됩니다. 그리고 재해로부터 쉽게 복구할 수 있습니다. 무엇보다도 큰 장점은 개발자 친화적이라는 것입니다.</p>
<p>이런 선언적 스타일은 쿠버네티스와 잘 어울립니다.</p>
<p>이미 아시고 계신분들도 있지만, 쿠버네티스의 주요한 개념 중 하나는 선언적 시스템이라는 것입니다. 어떠한 리소스를 생성하라 명령하는 것이 아니라, 사용자는 매니페스트를 정의하고, 시스템은 그 상태를 유지하기 위해 노력한다는 것입니다. 이런 점이 상당히 유사하기 때문에 잘 어울린다고 볼 수 있습니다.</p>
<p>앞서 설명한 바와 같이 GitOps란 Git 저장소에 있는 내용을, 쿠버네티스 클러스터에 그대로 반영해주는것입니다. 이것을 그림으로 표현하자면 아래와 같습니다.</p>
<p>Git 저장소에 있는것을 쿠버네티스 클러스터에 동기화 합니다.CI / CD 파이프라인</p>
<p><img src="/assets/img/2019/12/gitops-000.png" alt="GitOps%20and%20Kubernetes/Untitled.png" /></p>
<p>일반적으로 많이 사용하는 CI/CD 파이프라인을 대략적으로 그린다면, 다음과 같을 것입니다.</p>
<p><img src="/assets/img/2019/12/gitops-001.png" alt="GitOps%20and%20Kubernetes/Untitled%201.png" /></p>
<p>개발자가 소스 코드를 작성하고, Git 저장소에 올립니다. 그러면 Jenkins, CircleCI 같은 CI 툴에 의해서 테스트와 빌드 같은 작업이 실행된 후, 생성한 컨테이너 이미지를 컨테이너 저장소에 업로드 합니다.</p>
<p>그런 다음 CI/CD 툴에서 업로드된 컨테이너 이미지의 정보를 참조하여, 대상 서버에 배포를 하는 것입니다.</p>
<p>GitOps는 이러한 파이프라인의 배포 부분에서 약간 다르게 작동합니다.</p>
<p>컨테이너 이미지를 컨테이너 저장소에 업로드 한 후, 매니페스트가 저장되어 있는 Git 저장소를 가져옵니다. 그리고 매니페스트의 특정 부분(예를 들면 이미지 태그)을 업데이트 한 후, Git 저장소에 올리고 작업을 종료하게 됩니다.</p>
<p><img src="/assets/img/2019/12/gitops-002.png" alt="GitOps%20and%20Kubernetes/Untitled%202.png" /></p>
<p>매니페스트가 정의되어 있는 Git 저장소가 변경되면, Git 저장소의 내용과 쿠버네티스 클러스터를 동기화 해주는 에이전트가 변경 내역을 쿠버네티스 클러스터에 반영해 주게 되는 것입니다.</p>
<p>이 문서에는 편의를 위해서, Git 저장소의 내용과 쿠버네티스 클러스터를 동기화 해주는 역할을 하는 에이전트를 GitOps 오퍼레이터(Operator)라고 부르도록 하겠습니다.</p>
<p>이러한 과정을 간단히 코드로 표현하면 다음과 같습니다.</p>
<h3 id="새로운-컨테이너-이미지를-빌드하고-푸시하기">새로운 컨테이너 이미지를 빌드하고 푸시하기</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build -t example/hello:v2.0 .
docker push example/hello:v2.0
</code></pre></div></div>
<h3 id="매니페스트를-수정하고-git-저장소에-푸시하기">매니페스트를 수정하고 git 저장소에 푸시하기</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/example/hello-config.git
cd hello-config
kubectl patch --local -f config-deployment.yaml -p '{"spec":{"template":{"spec":{"containers":[{"name":"hello","image":"example/hello:v2.0"}]}}}}' -o yaml
git add . -m "Update hello to v2.0"
git push
</code></pre></div></div>
<p>이렇게 매니페스트가 저장되어 있는 git 저장소가 업데이트가 되면, GitOps Operator가 해당 내용을 쿠버네티스 클러스터에 반영 즉, 동기화 해주는 것입니다.</p>
<h1 id="gitops-operator">GitOps Operator</h1>
<p>GitOps 오퍼레이터에 대해서 좀 더 알아 보도록 하겠습니다. 앞서 살펴본 봐와 같이 GitOps 오퍼레이터는 Git 저장소 있는 매니페스트를 쿠버네티스 클러스터에 반영해 주는 역할을 합니다.</p>
<p>다음과 같이, 쿠버네티스의 <code class="language-plaintext highlighter-rouge">CronJob</code> 을 이용해서 간단히 구현해 볼 수도 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: gitops-cron-job
namespace: gitops
spec:
schedule: "*/10 * * * *"
backoffLimit: 0
jobTemplate:
spec:
template:
spec:
containers:
- name: gitops-operator
image: gitops/operator:latest
command: [sh, -e, -c]
args:
- git clone http://github.com/gitops/hello.git /tmp/hello
find /tmp/hello -name '*.yaml' -exec kubectl apply -f {} \;
</code></pre></div></div>
<p>10초 마다 주기적으로, Git 저장소의 내용을 가져와서 쿠버네티스 클러스터에 적용 하고 있는 것입니다.</p>
<p>이런 식으로 직접 구현해서 사용할 수도 있지만, 보안이나 모니터링 등 여러 측면에서 불편하기 때문에 기존에 만들어진 GitOps 오퍼레이터를 사용할 것입니다.</p>
<p>널리 알려진 GitOps 오퍼레이터는 <code class="language-plaintext highlighter-rouge">Weavework</code>에서 만든 <code class="language-plaintext highlighter-rouge">Flux</code>와 <code class="language-plaintext highlighter-rouge">Intuit</code>에서 만든 <code class="language-plaintext highlighter-rouge">ArgoCD</code>가 있습니다. 이 문서에서는 <code class="language-plaintext highlighter-rouge">Argo CD</code>를 사용합니다.</p>
<p><img src="/assets/img/2019/12/gitops-003.png" alt="GitOps%20and%20Kubernetes/Untitled%203.png" /></p>
<h1 id="argo-cd">Argo CD</h1>
<p><code class="language-plaintext highlighter-rouge">Argo CD</code>는 GitOps스타일의 배포를 지원하는 CD 도구입니다. 원하는 설정 사항을 변경하여 Git에 푸시하면, 자동으로 쿠버네티스 클러스터의 상태가 Git에 정의된 상태로 동기화 됩니다.</p>
<p>즉, 지정한 대상 환경에 애플리케이션을 원하는 상태로 자동으로 배포하는 것입니다.</p>
<p>그뿐만 아니라, 멀티 클러스터 관리/배포 기능도 가지고 있습니다. 그리고 SSO 연동과 멀티 테넌시를 지원하고, RBAC을 사용할 수도 있는 등 여러가지 장점을 가지고 있습니다.</p>
<p><img src="/assets/img/2019/12/argocd-ui.gif" alt="GitOps%20and%20Kubernetes/argocd-ui.gif" /></p>
<p>출처 : <a href="https://argoproj.github.io/argo-cd/">https://argoproj.github.io/argo-cd/</a></p>
<h1 id="gitops-구성하기">GitOps 구성하기</h1>
<h3 id="git-저장소">Git 저장소</h3>
<p><code class="language-plaintext highlighter-rouge">Argo CD</code>를 이용해서, GitOps 스타일의 CI/CD 파이프라인을 구성하는 방법에 대해서 알아보겠습니다.</p>
<p>이 예제에서는 두 개의 Git 저장소를 사용합니다.</p>
<ul>
<li>app 저장소 : 애플리케이션 소스 코드를 저장하고 있습니다.</li>
<li>config 저장소 : 쿠버네티스 배포 용 매니페스트를 저장하고 있습니다.</li>
</ul>
<p>물론 애플리케이션 소스 코드와 매니페스트를 단일 저장소에 저장할 수도 있습니다. 하지만 서로 다른 곳에 저장하는 것이 더 좋기 때문에 분리하는 것을 추천합니다.</p>
<p>app 저장소와 config 저장소를 분리하는 가장 큰 이유는, 용도와 생명 주기가 다르기 때문입니다. app 저장소는 실제 개발자가 주로 사용하며, 애플리케이션 소스코드를 저장하고 있고, config 저장소는 주로 CI/CD 툴 같은 자동화 시스템에서 주로 사용하기 때문입니다.</p>
<p>참고할 예제 소스 코드 저장소는 <a href="https://github.com/kangwoo/hello-go">https://github.com/kangwoo/hello-go</a> 이고, 매니페스트 저장소는 <a href="https://github.com/kangwoo/hello-go-deploy">https://github.com/kangwoo/hello-go-deploy</a> 입니다.</p>
<p>GitOps를 구성하기에 앞서, 먼저 결정해야 할 사항이 하나 있습니다. 그것은 바로 매니페스트를 어떻게 만들지 입니다. 기존에 사용하던 쿠버네티스트의 매니페스트를 그대로 사용해도 됩니다. 예를 들면 <code class="language-plaintext highlighter-rouge">deployment.yaml</code>, <code class="language-plaintext highlighter-rouge">service.yaml</code>, <code class="language-plaintext highlighter-rouge">ingress.yaml</code> 등등 기존에 사용하던 형태 그대로 만들어도 됩니다. 하지만, 배포 환경이 여러개가 된다는 등의 환경 별로 파일을 각자 만들어줘야하는 경우가 생길 수 있습니다. 물론 환경별로 파일을 따로 따로 만들 수도 있지만, 상당히 번거롭습니다. 중복되는 내용이 더 많을 것이기 때문입니다. 그래서 템플릿 같은 것을 사용하면 좀 더 편하게 만들 수 있습니다. 바로 <code class="language-plaintext highlighter-rouge">Kustomize</code>나 <code class="language-plaintext highlighter-rouge">Helm</code> 등의 툴을 이용하는것입니다. 다행히도 <code class="language-plaintext highlighter-rouge">Argo CD</code> 에서는 <code class="language-plaintext highlighter-rouge">Kustomize</code>나 <code class="language-plaintext highlighter-rouge">Helm</code>, <code class="language-plaintext highlighter-rouge">Ksonnet</code> 등을 지원하기 때문에, 별다른 노력 없이 해당 툴들을 사용할 수 있습니다. 예제에서는 <code class="language-plaintext highlighter-rouge">Kustomize</code> 를 사용하도록 하겠습니다.</p>
<p>우선 app 저장소에 소드 코드를 올립니다.</p>
<p>CI 툴을 이용해서, 해당 저장소에서 소스 코드를 클론한다음, 컨테이너 이미지를 빌드하고 푸시하는 파이프라인을 만듭니다. CI 툴은 Jenkins나 CircleCI, Tekton등 아무거나 사용해도 무방합니다. 여기서 중요하게 다를 부분은 GitOps 부분이기 때문에, 컨테이너 이미지를 빌드해서 푸시하는 것에 대해서는 자세히 다루지 않겠습니다.</p>
<p>go 로 작성된 Hello를 출력하는 main.go가 있고, 컨테이너 이미지 빌드를 위한 Dockerfile 이 있습니다. 그리고 젠킨스에서 CI 파이프라인을 정의한 Jenkinsfile 이 있습니다.</p>
<p>main.go</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":8080", nil)
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s", r.URL.Path[1:])
}
</code></pre></div></div>
<p>Dockerfile</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FROM golang:alpine as builder
RUN mkdir /build
ADD . /build/
WORKDIR /build
RUN go build -o main .
FROM alpine
ENV USER_UID=1001 \
APP_DIR=/app
RUN mkdir -p ${APP_DIR} && chown ${USER_UID}:0 ${APP_DIR} && chmod ug+rwx ${APP_DIR}
USER ${USER_UID}
COPY --from=builder /build/main ${APP_DIR}/
WORKDIR ${APP_DIR}
EXPOSE 8080
CMD ["./main"]
</code></pre></div></div>
<p>Jenkinsfile</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def podLabel = "worker-${UUID.randomUUID().toString()}"
podTemplate(label: podLabel, containers: [
containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'tools', image: 'argoproj/argo-cd-ci-builder:v1.0.1', command: 'cat', ttyEnabled: true),
],
volumes: [
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
node(label) {
def myRepo
stage('Checkout') {
myRepo = checkout scm
}
def gitCommit = myRepo.GIT_COMMIT
def shortGitCommit = "${gitCommit[0..7]}"
def imageTag = shortGitCommit
stage('Image Build') {
container('docker') {
sh "docker build . -t kangwoo/hello-go:${imageTag}"
}
}
stage('Image Push') {
container('docker') {
sh "docker push kangwoo/hello-go:${imageTag}"
}
}
stage('Deploy to dev') {
steps {
withCredentials([usernamePassword(credentialsId: 'my-git', usernameVariable: 'GIT_USER', passwordVariable: 'GIT_PWD')]) {
container('tools') {
sh "git clone https://${GIT_USER}:${GIT_PWD}@github.com/kangwoo/hello-go-deploy.git"
sh "git config --global user.email '${GIT_USER}@mycompany.com'"
dir("hello-go-deploy") {
sh "cd ./overlays/dev && kustomize edit set image kangwoo/hello-go:${imageTag}"
sh "git commit -am 'Publish new version ${imageTag} to dev' && git push || echo 'no changes'"
}
}
}
}
}
}
}
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Jenkinsfile</code>에서 중요하게 봐야할 부분은 <code class="language-plaintext highlighter-rouge">stage('Deploy to dev')</code> 입니다.</p>
<p>특정 이미지 태그를 만들어서, 컨테이너 이미지를 빌드하고 푸시한 다음, 해당 스테이지가 실행됩니다. 매니페스트가 정의되어 있는 Git 저장소를 클론하고, <code class="language-plaintext highlighter-rouge">kustomize edit set image</code> 명령어를 실행해서, 사용할 이미지 정보를 업데이트 해줍니다. 그런 다음 <code class="language-plaintext highlighter-rouge">git</code> 명령어를 이용해서 Git config 저장소에 푸시합니다.</p>
<p>변경된 매니페스트가 Git 저장소에 푸시되면, <code class="language-plaintext highlighter-rouge">Argo CD</code>가 변경된 점을 파악해서, 쿠버네티스 클러스터와 동기화 해줍니다.</p>
<p>참고로 <code class="language-plaintext highlighter-rouge">git</code>과 <code class="language-plaintext highlighter-rouge">kustomize</code> 명령어를 쉽게 사용하기 위해서, <code class="language-plaintext highlighter-rouge">argoproj/argo-cd-ci-builder:v1.0.1</code> 이미지를 사용하였습니다.</p>
<p>그 다음, 매니페스트를 만를고, config 저장소에 올립니다.</p>
<p>매니페스트는 <code class="language-plaintext highlighter-rouge">kustomize</code>를 사용해서 만들었습니다. 간단히 구조를 살펴보면, <code class="language-plaintext highlighter-rouge">base</code>와 <code class="language-plaintext highlighter-rouge">overlays</code> 디렉토리를 가지고 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── base
│ ├── deployment.yaml
│ ├── kustomization.yaml
│ └── service.yaml
└── overlays
├── dev
│ ├── deployment-patch.yaml
│ └── kustomization.yaml
└── prod
├── deployment-patch.yaml
└── kustomization.yaml
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">base</code> 디렉토리에는 리소스를 정의한 파일이 있습니다. 바로 <code class="language-plaintext highlighter-rouge">deployment.yaml</code>과 <code class="language-plaintext highlighter-rouge">service.yaml</code> 파일 입니다. 이 파일들에는 쿠버네티스의 <code class="language-plaintext highlighter-rouge">Deployment</code>와 <code class="language-plaintext highlighter-rouge">Service</code>를 생성하기 위한 명세가 담겨 있습니다. 그리고, <code class="language-plaintext highlighter-rouge">kustomization.yaml</code> 라는 파일도 존재하는데, 이 파일은 <code class="language-plaintext highlighter-rouge">kustomize</code> 에서 사용하는 파일로서, 기본적인 메타 정보와 어떠한 리소스들을 사용할지에 대한 정보가 담겨 있습니다.</p>
<p><code class="language-plaintext highlighter-rouge">overlays</code> 디렉토리는 다시 <code class="language-plaintext highlighter-rouge">dev</code>와 <code class="language-plaintext highlighter-rouge">prod</code> 디렉토리로 나누어 집니다. 개발과 프로덕션 환경으로 사용하기 위해서 두 개로 나눈것입니다. <code class="language-plaintext highlighter-rouge">dev</code>와 <code class="language-plaintext highlighter-rouge">prod</code> 디렉토리에는 각각 메타 정보를 담긴 <code class="language-plaintext highlighter-rouge">kustomization.yaml</code> 파일과, 환경별로 패치할 내용이 담긴 deployment-patch.yaml 파일이 존재합니다. 예를 들면, 개발 환경에 반영될때에는, <code class="language-plaintext highlighter-rouge">base</code> + <code class="language-plaintext highlighter-rouge">overlays/dev</code> 가 합쳐진 결과가 반영이 되는 것입니다.</p>
<p><code class="language-plaintext highlighter-rouge">Argo CD</code> 는 <code class="language-plaintext highlighter-rouge">kustomize</code> 를 지원하기 때문에, <code class="language-plaintext highlighter-rouge">overlays/dev</code> 같이 해당 디렉토리를 지정해주면, 합쳐진 결과가 자동으로 쿠버네티스 클러스터에 동기화 됩니다.</p>
<h3 id="argo-cd-1">Argo CD</h3>
<p>Git 저장소에 있는 내용을 쿠버네티스 클러스터에 자동으로 동기화 하기 위해서 <code class="language-plaintext highlighter-rouge">Argo CD</code> 에 설정을 추가하겠습니다.</p>
<p><code class="language-plaintext highlighter-rouge">Argo CD</code> 웹 화면에 접속한 후, 로그인을 한다음, <code class="language-plaintext highlighter-rouge">New App</code>을 클릭합니다.</p>
<p><img src="/assets/img/2019/12/gitops-004.png" alt="GitOps%20and%20Kubernetes/Untitled%204.png" /></p>
<p>그리고 아래 값들을 입력한 후, <code class="language-plaintext highlighter-rouge">CREATE</code> 버튼을 클릭하면 애플리케이션이 생성됩니다.</p>
<p><img src="/assets/img/2019/12/argocd-fields.png" alt="New App" /></p>
<p>웹 화면을 사용하지 않고, 직접 CR을 생성해서 사용할 수도 있습니다.</p>
<p>아래처럼 <code class="language-plaintext highlighter-rouge">Application</code> 리소스를 정의한 후, <code class="language-plaintext highlighter-rouge">Argo CD</code> 가 설치된 네임스페이스에 해당 리소스를 생성해 주면 됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat <<EOF | kubectl -n argocd apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: hello-go
spec:
destination:
namespace: default
server: https://kubernetes.default.svc
project: default
source:
path: overlays/dev
repoURL: https://github.com/kangwoo/hello-go-deploy.git
targetRevision: HEAD
syncPolicy:
automated: {}
EOF
</code></pre></div></div>
<p>애플리케이션이 정상적으로 생성되면, 화면에서 확인할 수 있습니다. 애플리케이션 이름을 클릭하면 다음과 같은 상세 내용을 볼 수 있습니다.</p>
<p><img src="/assets/img/2019/12/gitops-005.png" alt="GitOps%20and%20Kubernetes/Untitled%205.png" /></p>강우kangwoo@gmail.comGitOps 스타일의 지속적 배포Kubernetes 커맨드 라인 툴2019-12-02T11:21:00+00:002019-12-02T11:21:00+00:00https://kangwoo.github.io/devops/kubernetes/kubernetes-command-line-tools<h2 id="소개글">소개글</h2>
<p>쿠버네티스 클러스터를 자주 사용하는 사람이라면, 반복적으로 명령을 입력하는데 불편함을 느낄것입니다.
이러한 불편함을 줄이기 위한 방법은 <code class="language-plaintext highlighter-rouge">kubectl</code>에 CLI 도구들을 사용하는것입니다.
이 문서에서는 <code class="language-plaintext highlighter-rouge">kubectl</code>의 사용을 도와줄 몇가지 도구들을 소개하겠습니다.</p>
<h2 id="kubectl-aliases--별칭-주기">kubectl-aliases : 별칭 주기</h2>
<p>매번 <code class="language-plaintext highlighter-rouge">kubectl</code>이나 다른 명령어를 입력할 수 있지만, 상당히 불편함을 느낄것입니다.
이런 불편함을 줄이는 가장 쉬운 방법은 별칭(alias)를 지정해서 사용하는 것입니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alias k='kubectl'
alias kg='kubectl get'
alias kgpo='kubectl get pod'
...
</code></pre></div></div>
<p>위의 예제처럼 별칭을 지정해 놓으면, 보다 효율적으로 명령어를 입력할 수 있습니다.
자주 쓰는 명령어는 <a href="https://github.com/ahmetb/kubectl-aliases">kubectl-aliases</a>에 정의되어 있습니다. 참고하시기 바랍니다.</p>
<h2 id="shell-autocompletion--쉡-자동완성-하기">Shell Autocompletion : 쉡 자동완성 하기</h2>
<p><code class="language-plaintext highlighter-rouge">kubectl</code>의 명령어를 일일이 입력하고 있다면, 쉘 자동 완성을 설정하는게 좋습니다.
쉘에 <code class="language-plaintext highlighter-rouge">kubectl</code> 입력하고 [Tab]키를 눌러서 자동완성을 시도하면, 추천 단어 목록을 보여주거나, 추천 단어가 1개일 경우 자동으로 완성이 됩니다.
<code class="language-plaintext highlighter-rouge">kubectl</code>이 대한 쉘 자동 완성을 설정하는 방법은 <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion">설치 문서</a>를 참고하시기 바랍니다.</p>
<h3 id="자동-완성-사용하기">자동 완성 사용하기</h3>
<p>예를 들어서 <code class="language-plaintext highlighter-rouge">g</code>를 입력하고 <code class="language-plaintext highlighter-rouge">tab</code>을 누르면, <code class="language-plaintext highlighter-rouge">get</code>으로 자동 완성됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl g [Tab]
$ kubectl get
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">get p</code>를 입력하고 <code class="language-plaintext highlighter-rouge">tab</code>을 누르면, <code class="language-plaintext highlighter-rouge">p</code>로 시작하는 단어 목록을 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get p [Tab]
persistentvolumeclaims podsecuritypolicies.policy
persistentvolumes podtemplates
poddisruptionbudgets.policy policies.authentication.istio.io
pods priorityclasses.scheduling.k8s.io
podsecuritypolicies.extensions
</code></pre></div></div>
<h2 id="kubectx--kubecns--컨텍스트와-네임스페이스-쉽게-변경하기">kubectx & kubecns : 컨텍스트와 네임스페이스 쉽게 변경하기</h2>
<p>쿠버네티스의 컨텍스트를 여러개 사용하고 있거나, 네임스페이스를 여러개 사용하고 있다면, 이 도구들이 도움이 될것입니다.
<code class="language-plaintext highlighter-rouge">kubectx</code>는 컨텍스트를 쉽게 변경할 수 있도록 도움을 줍니다.
이 도구를 사용하면, <code class="language-plaintext highlighter-rouge">kubectl config use-context greentea</code> 같은 긴 명령어를 사용하지 않아도 됩니다.
그리고 <code class="language-plaintext highlighter-rouge">kubens</code>는 기본 네임스페이스를 변경할 수 있도록 도와줍니다.
이 두 도구 모드 [Tab] 완성을 지원합니다. 그 뿐만 아니라, <code class="language-plaintext highlighter-rouge">fzf</code>를 설치하면 대화식 메뉴를 제공하기 때문에, 더욱 편리하게 사용할 수 있습니다.</p>
<p><code class="language-plaintext highlighter-rouge">kubectx</code> 와 <code class="language-plaintext highlighter-rouge">kubens</code>에 대한 설정 방법은 <a href="https://github.com/ahmetb/kubectx">설치 문서</a>를 참고하시기 바랍니다.</p>
<h3 id="kubectx-사용하기">kubectx 사용하기</h3>
<p><code class="language-plaintext highlighter-rouge">kubectx</code> 명령을 실행하면, 컨텍스트 목록을 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectx
coffee
greentea
</code></pre></div></div>
<p>컨텍스트를 변경하기 위해서는, 컨텍스트 명을 입력하면 됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectx greentea
Switched to context "greentea".
</code></pre></div></div>
<p>만약 <code class="language-plaintext highlighter-rouge">fzf</code>가 설치되어 있으면, <code class="language-plaintext highlighter-rouge">kubectx</code> 명령을 실행하면 대화식 메뉴를 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectx
> coffee
greentea
2/2
</code></pre></div></div>
<h3 id="kubens-사용하기">kubens 사용하기</h3>
<p><code class="language-plaintext highlighter-rouge">kubens</code> 명령을 실행하면, 네임스페이스 목록을 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubens
kube-system
kube-public
istio-system
default
</code></pre></div></div>
<p>네임스페이스를 변경하기 위해서는, 네임스페이스 명을 입력하면 됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubens kube-system
Context "greentea" modified.
Active namespace is "kube-system".
</code></pre></div></div>
<p>만약 <code class="language-plaintext highlighter-rouge">fzf</code>가 설치되어 있으면, <code class="language-plaintext highlighter-rouge">kubens</code> 명령을 실행하면 대화식 메뉴를 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectx
> kube-system
kube-public
istio-system
default
4/4
</code></pre></div></div>
<h2 id="kube-ps1--컨템스트와-네임스페이스-이름을-쉘-프로프트에-보여주기">kube-ps1 : 컨템스트와 네임스페이스 이름을 쉘 프로프트에 보여주기</h2>
<p>쿠버네티스의 컨텍스트나 네임스페이스를 여러개 사용하고 있을때, 현재 어떤 컨텍스트와 네임스페이스를 사용하고 있는지 헷갈리는 경우가 많습니다.
<code class="language-plaintext highlighter-rouge">kube-ps1</code>은 현재 사용하고 있는 쿠버네티스 컨텍스트 및 네임스페이스를 쉘의 프롬프 문자열에 보여줍니다.
이 도구를 사용하면, <code class="language-plaintext highlighter-rouge">kubectl config current-context</code> 같은 긴 명령어를 사용하지 않아도 됩니다.
그리고, 컨텍스트와 네임스페이스를 보는게 불편하다면, <code class="language-plaintext highlighter-rouge">kubeoff</code> 명령어를 실행해서 <code class="language-plaintext highlighter-rouge">kube-ps</code>을 비활성화 시킬수도 있습니다.
물론, 다시 컨텍스트와 네임스페이스를 보고 싶다면 <code class="language-plaintext highlighter-rouge">kubeon</code> 명령어를 실행하면 됩니다.
<code class="language-plaintext highlighter-rouge">kube-ps1</code>`에 대한 설정 방법은 <a href="https://github.com/jonmosco/kube-ps1">설치 문서</a>를 참고하시기 바랍니다.</p>
<p><code class="language-plaintext highlighter-rouge">kube-ps1</code>의 설치가 완료되면, 셀의 프롬프트가 다음처럼 표시됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(⎈ |[context]:[namespace]) $
(⎈ |greentea:kube-system) $
</code></pre></div></div>
<h2 id="stern--여러개의-팟pod이나-컨테이너의-로그를-쉽게-보기">stern : 여러개의 팟(pod)이나 컨테이너의 로그를 쉽게 보기</h2>
<p><code class="language-plaintext highlighter-rouge">kubectl</code>은 기본적으로 팟이나 컨테이너의 로그를 테일링해서 볼 수 있는 기능을 제공하고 있습니다.
하지만, 팟이 여러개이거나 하나의 팟에 여러개의 컨테이너가 있을 경우는 로그른 테일링 해서 볼 수가 없습니다.</p>
<p><code class="language-plaintext highlighter-rouge">stern</code>는 쿠버네티스트의 여러 팟이나, 팟 내의 여러 컨테이너의 로그를 테일링 할 수 있도록 해줍니다.
그리고 대상별로 출력되는 로그가 색상별로 표현되기 때문에 쉽게 구별할 수 있습니다.
<code class="language-plaintext highlighter-rouge">stern</code>`에 대한 설정 방법은 <a href="https://github.com/wercker/stern">설치 문서</a>를 참고하시기 바랍니다.</p>
<p><code class="language-plaintext highlighter-rouge">stern ginger</code>이라는 명령어를 실행하면, <code class="language-plaintext highlighter-rouge">ginger</code>이라는 이름을 가진 팟에 속한 컨테니이너들의 로그를 모두 보여줍니다.
이 쿼리는 정규식이기 때문에 팟 이름을 쉽게 필터링할 수 있으며, 정확한 이름을 지정하지 않아도 됩니다.
다시 말해성 <code class="language-plaintext highlighter-rouge">ginger</code>로 시작하는 이름을 가진 모든 팟들의 로그를 보여주게 되는것입니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ stern ginger
hello gingersnap istio-proxy 2019-12-02T09:05:41.739784Z info watchFileEvents: "/etc/certs": MODIFY|ATTRIB
hello gingersnap istio-proxy 2019-12-02T09:05:41.739842Z info watchFileEvents: "/etc/certs/..2019_11_06_05_09_24.409981738": MODIFY|ATTRIB
...
</code></pre></div></div>
<p>그리고 아래처럼, 레이블을 가지고 쿼리할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stern --all-namespaces -l run=cookie
</code></pre></div></div>
<h2 id="k9s--터미널-ui">k9s : 터미널 UI</h2>
<p><code class="language-plaintext highlighter-rouge">k9s</code>는 쿠버네티스 클러스터와 상호 작용할 수 있는 터미널 UI를 제공합니다.
이 도구는 쿠버네티스 리소스들을 쉽게 탐색하고 관리할 수 있도록 도와줍니다.
‘k9s’에 대한 설정 방업은 <a href="https://github.com/derailed/k9s">설치 문서</a>를 참고하시기 바랍니다.</p>
<p>Pod 조회하기
<img src="/assets/img/2019/12/k9s-po.png" alt="pods" /></p>
<p>로그 보기
<img src="/assets/img/2019/12/k9s-logs.png" alt="logs" /></p>강우kangwoo@gmail.com소개글 쿠버네티스 클러스터를 자주 사용하는 사람이라면, 반복적으로 명령을 입력하는데 불편함을 느낄것입니다. 이러한 불편함을 줄이기 위한 방법은 kubectl에 CLI 도구들을 사용하는것입니다. 이 문서에서는 kubectl의 사용을 도와줄 몇가지 도구들을 소개하겠습니다.Ingress status에 값이 없을 경우2019-11-11T11:31:00+00:002019-11-11T11:31:00+00:00https://kangwoo.github.io/devops/kubernetes/ingress-status-is-empty<h2 id="ingress-status에-값이-없다">Ingress status에 값이 없다.</h2>
<p><code class="language-plaintext highlighter-rouge">argocd</code>를 이용해서, 리소스들을 동기화 했는데, <code class="language-plaintext highlighter-rouge">ingress</code> 부분 계속 <code class="language-plaintext highlighter-rouge">Processing</code>이라고 나오이고 끝날 생각을 안한다.
<code class="language-plaintext highlighter-rouge">ingress</code> 의 상태를 보니 다음과 같았다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get ingresses dobby -o yaml
...
status:
loadBalancer: {}
</code></pre></div></div>
<p>뭔가 저기 <code class="language-plaintext highlighter-rouge">status</code>에 값이 할당되어야 할거 같은 느낌이 들었지만, 왜 안되어 있는지 이유는 알지 못했다.
그래서 사용중인 <code class="language-plaintext highlighter-rouge">nginx-ingress-controller</code>의 설정을 살펴보았다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
...
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">--publish-service</code> 플래그를 사용해서, <code class="language-plaintext highlighter-rouge">ingress-nginx</code> 서비스의 IP를 읽어와서 업데이트 해주는거 같은데,
불행히도 해당 <code class="language-plaintext highlighter-rouge">ingress-nginx</code> 서비스가 <code class="language-plaintext highlighter-rouge">LoadBalancer</code>이 아니라서 IP가 존재하지 않는다.
그래서 <code class="language-plaintext highlighter-rouge">--publish-service</code> 플래그를 삭제하고, <code class="language-plaintext highlighter-rouge">--publish-service</code> 플래그를 사용해서 직접 명시해주었다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-status-address=10.xx.yy.zz
- --annotations-prefix=nginx.ingress.kubernetes.io
...
</code></pre></div></div>
<p>변경사항을 반영하고, <code class="language-plaintext highlighter-rouge">ingress</code> 의 상태를 조회를 해보니, 정상적으로 상태값이 반영되었다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get ingresses dobby -o yaml
...
status:
loadBalancer:
ingress:
- ip: 10.xx.yy.zz
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">ingress</code> 의 상태값이 반영된 후, <code class="language-plaintext highlighter-rouge">argocd</code>도 정상적으로 작동하였다.</p>
<h2 id="참고">참고</h2>
<ul>
<li>https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/static-ip/README.md</li>
</ul>강우kangwoo@gmail.comIngress status에 값이 없다. argocd를 이용해서, 리소스들을 동기화 했는데, ingress 부분 계속 Processing이라고 나오이고 끝날 생각을 안한다. ingress 의 상태를 보니 다음과 같았다. $ kubectl get ingresses dobby -o yaml ... status: loadBalancer: {}Prometheus Operator로 kube-proxy 모니터링 하기2019-10-09T08:41:00+00:002019-10-09T08:41:00+00:00https://kangwoo.github.io/devops/kubernetes/monitoring-kube-proxy-with-prometheus-operator<h2 id="kube-proxy-모니터링-하기">kube-proxy 모니터링 하기</h2>
<p><code class="language-plaintext highlighter-rouge">kube-proxy</code>도 <code class="language-plaintext highlighter-rouge">/metrics</code>라는 매트릭 엔드 포인트를 제공한다.
하지만 기본 설정값이 <code class="language-plaintext highlighter-rouge">127.0.0.1:10249</code>이기 때문에, 외부에서 접근이 안된다.</p>
<p>그래서 <code class="language-plaintext highlighter-rouge">prometheus</code>에서 수집하려고 하면, 접근이 안되서 문제가 발생한다.</p>
<h2 id="설정-변경하기">설정 변경하기</h2>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl <span class="nt">-n</span> kube-system edit cm/kube-proxy
<span class="c">## Change from</span>
metricsBindAddress: 127.0.0.1:10249
<span class="c">## Change to</span>
metricsBindAddress: 0.0.0.0:10249
</code></pre></div></div>
<p>물론 <code class="language-plaintext highlighter-rouge">0.0.0.0:10249</code>로 변경하고, 모든 곳에서 다 접근이 가능하기 때문에,
보안이 취약한 곳이라면 사용하지 않는것이 좋다.</p>
<p>설정을 변경하고, <code class="language-plaintext highlighter-rouge">kube-proxy</code>를 재시작하면 적용이 된다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl <span class="nt">-n</span> kube-system delete pod <span class="nt">-l</span> k8s-app<span class="o">=</span>kube-proxy
</code></pre></div></div>강우kangwoo@gmail.comkube-proxy 모니터링 하기 kube-proxy도 /metrics라는 매트릭 엔드 포인트를 제공한다. 하지만 기본 설정값이 127.0.0.1:10249이기 때문에, 외부에서 접근이 안된다.Prometheus Operator로 etcd 모니터링 하기2019-10-09T05:27:00+00:002019-10-09T05:27:00+00:00https://kangwoo.github.io/devops/kubernetes/monitoring-etcd-with-prometheus-operator<h2 id="etcd-모니터링-하기">etcd 모니터링 하기</h2>
<p><code class="language-plaintext highlighter-rouge">etcd</code>는 <code class="language-plaintext highlighter-rouge">/metrics</code> 라는, 프로메테우스가 수집할 수 있는 매트릭 엔드 포인트를 제공한다.
하지만, Secure Etcd 클러스터인 경우에는 해당 엔드 포인트에 접근하기 위해서는 인증서가 필요하다.</p>
<p>(다른 방법으로는 <code class="language-plaintext highlighter-rouge">/metrics</code> 엔드 포인트를 다른 포트로 분리하여, 인증서 없이 접근할 수도 있다. <code class="language-plaintext highlighter-rouge">--listen-metrics-urls</code> 옵션을 참고 바란다.)</p>
<h2 id="환경">환경</h2>
<p><code class="language-plaintext highlighter-rouge">helm</code>을 사용해서 <code class="language-plaintext highlighter-rouge">prometheus-operator</code>를 설치할 것이다.
그래서 <code class="language-plaintext highlighter-rouge">prometheus-operator</code>를 설치할때, <code class="language-plaintext highlighter-rouge">etcd</code>를 모니터링하도록 설정 파일을 변경해서 사용한다.</p>
<h2 id="valuesyaml-수정하기">values.yaml 수정하기</h2>
<h3 id="kubeetcd">kubeEtcd</h3>
<p><code class="language-plaintext highlighter-rouge">kubeEtcd.serviceMonitor</code>의 값들을 변경한다. <code class="language-plaintext highlighter-rouge">scheme</code>를 https로 변경하고, 인증서 정보를 등록한다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">## Component scraping etcd</span>
<span class="c">##</span>
kubeEtcd:
...
serviceMonitor:
scheme: https
insecureSkipVerify: <span class="nb">false
</span>serverName: localhost
caFile: /etc/prometheus/secrets/etcd-client-cert/etcd-ca
certFile: /etc/prometheus/secrets/etcd-client-cert/etcd-client
keyFile: /etc/prometheus/secrets/etcd-client-cert/etcd-client-key
...
</code></pre></div></div>
<h3 id="prometheus">prometheus</h3>
<p>프로메테우스를 기동할때 <code class="language-plaintext highlighter-rouge">etcd-client-cert</code>란 이름의 <code class="language-plaintext highlighter-rouge">secret</code>를 <code class="language-plaintext highlighter-rouge">pod</code>에 마운트하기 위해서,
<code class="language-plaintext highlighter-rouge">prometheus.secrets</code>에 <code class="language-plaintext highlighter-rouge">etcd-client-cert</code>를 추가해 준다.
그리고, <code class="language-plaintext highlighter-rouge">etcd</code> 스크랩 설정 추가를 위해서,
<code class="language-plaintext highlighter-rouge">prometheus.additionalScrapeConfigs</code>의 <code class="language-plaintext highlighter-rouge">kube-etcd</code> 부분을 활성화 해준다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">## Deploy a Prometheus instance</span>
<span class="c">##</span>
prometheus:
...
secrets:
- <span class="s2">"etcd-client-cert"</span>
...
additionalScrapeConfigs:
- job_name: kube-etcd
kubernetes_sd_configs:
- role: node
scheme: https
tls_config:
ca_file: /etc/prometheus/secrets/etcd-client-cert/etcd-ca
cert_file: /etc/prometheus/secrets/etcd-client-cert/etcd-client
key_file: /etc/prometheus/secrets/etcd-client-cert/etcd-client-key
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_<span class="o">(</span>.+<span class="o">)</span>
- source_labels: <span class="o">[</span>__address__]
action: replace
target_label: __address__
regex: <span class="o">([</span>^:<span class="p">;</span><span class="o">]</span>+<span class="o">)</span>:<span class="o">(</span><span class="se">\d</span>+<span class="o">)</span>
replacement: <span class="k">${</span><span class="nv">1</span><span class="k">}</span>:2379
- source_labels: <span class="o">[</span>__meta_kubernetes_node_name]
action: keep
regex: .<span class="k">*</span>mst.<span class="k">*</span>
- source_labels: <span class="o">[</span>__meta_kubernetes_node_name]
action: replace
target_label: node
regex: <span class="o">(</span>.<span class="k">*</span><span class="o">)</span>
replacement: <span class="k">${</span><span class="nv">1</span><span class="k">}</span>
metric_relabel_configs:
- regex: <span class="o">(</span>kubernetes_io_hostname|failure_domain_beta_kubernetes_io_region|beta_kubernetes_io_os|beta_kubernetes_io_arch|beta_kubernetes_io_instance_type|failure_domain_beta_kubernetes_io_zone<span class="o">)</span>
action: labeldrop
...
</code></pre></div></div>
<h2 id="인증서-복사하기">인증서 복사하기</h2>
<p><code class="language-plaintext highlighter-rouge">etcd</code> 인증서를, 프로메테스를 설치할 <code class="language-plaintext highlighter-rouge">monitoring</code> 네임스페이스에,
<code class="language-plaintext highlighter-rouge">etcd-client-cert</code>란 이름의 <code class="language-plaintext highlighter-rouge">secret</code>로 복사한다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">POD_NAME</span><span class="o">=</span><span class="si">$(</span>kubectl get pods <span class="nt">-o</span><span class="o">=</span><span class="nv">jsonpath</span><span class="o">=</span><span class="s1">'{.items[0].metadata.name}'</span> <span class="nt">-l</span> <span class="nv">component</span><span class="o">=</span>kube-apiserver <span class="nt">-n</span> kube-system<span class="si">)</span>
kubectl create secret generic etcd-client-cert <span class="nt">-n</span> monitoring <span class="se">\</span>
<span class="nt">--from-literal</span><span class="o">=</span>etcd-ca<span class="o">=</span><span class="s2">"</span><span class="si">$(</span>kubectl <span class="nb">exec</span> <span class="nv">$POD_NAME</span> <span class="nt">-n</span> kube-system <span class="nt">--</span> <span class="nb">cat</span> /etc/kubernetes/pki/etcd/ca.crt<span class="si">)</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">--from-literal</span><span class="o">=</span>etcd-client<span class="o">=</span><span class="s2">"</span><span class="si">$(</span>kubectl <span class="nb">exec</span> <span class="nv">$POD_NAME</span> <span class="nt">-n</span> kube-system <span class="nt">--</span> <span class="nb">cat</span> /etc/kubernetes/pki/etcd/healthcheck-client.crt<span class="si">)</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">--from-literal</span><span class="o">=</span>etcd-client-key<span class="o">=</span><span class="s2">"</span><span class="si">$(</span>kubectl <span class="nb">exec</span> <span class="nv">$POD_NAME</span> <span class="nt">-n</span> kube-system <span class="nt">--</span> <span class="nb">cat</span> /etc/kubernetes/pki/etcd/healthcheck-client.key<span class="si">)</span><span class="s2">"</span>
</code></pre></div></div>
<h2 id="helm으로-prometheus-operator-설치하기"><code class="language-plaintext highlighter-rouge">helm</code>으로 <code class="language-plaintext highlighter-rouge">prometheus-operator</code> 설치하기</h2>
<p><code class="language-plaintext highlighter-rouge">helm</code> 설치 명령어로, <code class="language-plaintext highlighter-rouge">proemtheus-operator</code>를 설치한다.
수정한 설정값을 적용하기 위해서 <code class="language-plaintext highlighter-rouge">--values values.yaml</code> 옵션을 사용한다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>helm <span class="nb">install </span>stable/prometheus-operator <span class="nt">--name</span> mon <span class="nt">--namespace</span> monitoring <span class="nt">--values</span> values.yaml <span class="nt">--tls</span>
</code></pre></div></div>
<h2 id="참고-사항">참고 사항</h2>
<p><code class="language-plaintext highlighter-rouge">helm</code>으로 <code class="language-plaintext highlighter-rouge">prometheus-operator</code>를 삭제할때, <code class="language-plaintext highlighter-rouge">crd</code>는 자동으록 삭제되지 않는다.
아래 명령어로 직접 삭제해야한다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl delete <span class="nt">--ignore-not-found</span> customresourcedefinitions <span class="se">\</span>
prometheuses.monitoring.coreos.com <span class="se">\</span>
servicemonitors.monitoring.coreos.com <span class="se">\</span>
podmonitors.monitoring.coreos.com <span class="se">\</span>
alertmanagers.monitoring.coreos.com <span class="se">\</span>
prometheusrules.monitoring.coreos.com
</code></pre></div></div>
<h2 id="참고-링크">참고 링크</h2>
<ul>
<li>https://github.com/helm/charts/tree/master/stable/prometheus-operator</li>
<li>https://github.com/kubernetes-monitoring/kubernetes-mixin</li>
<li>https://github.com/etcd-io/etcd/blob/master/Documentation/op-guide/monitoring.md</li>
</ul>강우kangwoo@gmail.cometcd 모니터링 하기 etcd는 /metrics 라는, 프로메테우스가 수집할 수 있는 매트릭 엔드 포인트를 제공한다. 하지만, Secure Etcd 클러스터인 경우에는 해당 엔드 포인트에 접근하기 위해서는 인증서가 필요하다.Helm v22019-09-09T11:17:00+00:002019-09-09T11:17:00+00:00https://kangwoo.github.io/devops/kubernetes/helm-v2<h1 id="helm">Helm</h1>
<p><code class="language-plaintext highlighter-rouge">Helm</code>은 쿠버네티스 패키지 관리 툴이다.
<code class="language-plaintext highlighter-rouge">chart</code>라고 부르는, 이미 만들어 놓은 패키지 명세서를 이용해서 손쉽게 애플리케이션을 배포하고 관리할 수 있다.</p>
<p>사용의 편의성을 제공하기는 하지만, v2까지는 권한 문제로 인해서 약간의 불편한 점이 있다.</p>
<p>멀티 테넌시 환경의 쿠버네티스 클러스터에서 사용할 경우, 각 사용자의 권한별로 리소스 접근을 제어하기가 힘들다.
네임스페이스별로 <code class="language-plaintext highlighter-rouge">tiller</code>를 설치하고, 인증서를 관리할 수 있지만, 상당히 불편하다.
이러한 문제의 근본적인 이유는 패키지 설치를 실행하는 사용자의 권한으로 리소스를 설치하는 것이 아니라,
<code class="language-plaintext highlighter-rouge">tiller</code>가 가진 권한으로 리소스가 설치되기 때문이다.
즉, 나에게 권한이 없어도, <code class="language-plaintext highlighter-rouge">tiller</code>에 권한이 있다면, 내 권한 밖의 리소스를 제어할 수 있는것이다.</p>
<p>다행히도 새로 만들어진 v3 부터는 이러한 문제가 해결될 것으로 보인다.</p>
<p>이 글을 쓰는 시점에서는 아직 v3가 정식 릴리즈 되지 않았다.
그래서 어쩔 수 없이 v2를 사용하였고, v2을 멀티 테넌시 환경에서 사용하기 쉽도록 하기 위해서 <code class="language-plaintext highlighter-rouge">kubeapps</code>을 사용했다.</p>
<h1 id="kubeapps">Kubeapps</h1>
<p><code class="language-plaintext highlighter-rouge">Kubeapps</code>는 쿠버네티스트 클러스터에 애플리케이션을 배포하고 관리할 수 있게 도와주는 웹 기반의 UI 애플리케이션이다.
<code class="language-plaintext highlighter-rouge">Kubeapps</code>는 ‘helm chart’를 사용할 수 있을 뿐 아니라, 사용자 기반의 권한 제어 기능도 제공한다.</p>
<p><img src="/assets/img/2019/09/kubeapps-applications.png" alt="Applications" /></p>
<p><img src="/assets/img/2019/09/kubeapps-catalog.png" alt="Catalog" /></p>
<h1 id="준비물">준비물</h1>
<ul>
<li>RBAC 기반의 쿠버네티스 클러스터</li>
<li>OIDC Provider + 쿠버네티스 연동</li>
</ul>
<h1 id="helm-설치하기">Helm 설치하기</h1>
<h2 id="helm-설치">Helm 설치</h2>
<p>‘helm’을 설치한다.</p>
<p>개발 환경이 <code class="language-plaintext highlighter-rouge">mac</code>이라서 <code class="language-plaintext highlighter-rouge">brew</code>를 사용해서 간단히 설치하였다. 환경이 다르다면, <a href="https://helm.sh/docs/using_helm/#installing-helm">helm 문서</a>를 참고하길 바란다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">$ brew install kubernetes-helm</span>
</code></pre></div></div>
<h2 id="using-ssltls-between-helm-and-tiller">Using SSL/TLS Between Helm and Tiller</h2>
<p>‘helm’ v2를 사용려면, 쿠버네티스 클러스터에 <code class="language-plaintext highlighter-rouge">Tiller</code>가 설치되어 있어야한다.
기본값으로 <code class="language-plaintext highlighter-rouge">Tiller</code>를 설치할 경우 보안상의 문제가 있기때문에 TLS 인증서를 사용하는 형태로 설치한다.</p>
<h2 id="ca-만들기">CA 만들기</h2>
<p><code class="language-plaintext highlighter-rouge">openssl</code> 툴을 이용해서, CA를 생성한다.</p>
<ul>
<li>CA용 개인키를 생성한다.
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>openssl genrsa <span class="nt">-out</span> ./ca.key.pem 4096
Generating RSA private key, 4096 bit long modulus
..........................++
.........................................++
e is 65537 <span class="o">(</span>0x010001<span class="o">)</span>
</code></pre></div> </div>
</li>
<li>CA용 인증서를 생성한다.
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>openssl req <span class="nt">-key</span> ca.key.pem <span class="nt">-new</span> <span class="nt">-x509</span> <span class="nt">-days</span> 7300 <span class="nt">-sha256</span> <span class="nt">-out</span> ca.cert.pem <span class="nt">-extensions</span> v3_ca
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter <span class="s1">'.'</span>, the field will be left blank.
<span class="nt">-----</span>
Country Name <span class="o">(</span>2 letter code<span class="o">)</span> <span class="o">[</span>AU]:KR
State or Province Name <span class="o">(</span>full name<span class="o">)</span> <span class="o">[</span>Some-State]:Gyeonggi-do
Locality Name <span class="o">(</span>eg, city<span class="o">)</span> <span class="o">[]</span>:Seongnam
Organization Name <span class="o">(</span>eg, company<span class="o">)</span> <span class="o">[</span>Internet Widgits Pty Ltd]:tiller
Organizational Unit Name <span class="o">(</span>eg, section<span class="o">)</span> <span class="o">[]</span>:
Common Name <span class="o">(</span>e.g. server FQDN or YOUR name<span class="o">)</span> <span class="o">[]</span>:tiller
Email Address <span class="o">[]</span>:tiller@example.com
</code></pre></div> </div>
<p>이렇게 생성한 CA를 이용해서, <code class="language-plaintext highlighter-rouge">Tiller</code>와 <code class="language-plaintext highlighter-rouge">Helm client</code>을 인증서를 만들것이다.</p>
</li>
</ul>
<h2 id="tiller-인증서-만들기"><code class="language-plaintext highlighter-rouge">Tiller</code> 인증서 만들기</h2>
<ul>
<li>Tiller용 개인키를 생성한다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ openssl genrsa -out ./tiller.key.pem 4096
Generating RSA private key, 4096 bit long modulus
..........................................................++
.................................++
e is 65537 (0x010001)
</code></pre></div></div>
<ul>
<li>Tiller용 인증서를 생성한다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ openssl req -key tiller.key.pem -new -sha256 -out tiller.csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:KR
State or Province Name (full name) [Some-State]:Gyeonggi-do
Locality Name (eg, city) []:Seongnam
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Tiller Server
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:tiller-server
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
</code></pre></div></div>
<ul>
<li>Tiller용 인증서를 CA의 인증서로 서명한다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in tiller.csr.pem -out tiller.cert.pem -days 365
Signature ok
subject=C = KR, ST = Gyeonggi-do, L = Seongnam, O = Tiller Server, CN = tiller-server
Getting CA Private Key
</code></pre></div></div>
<h2 id="helm-client-인증서-만들기"><code class="language-plaintext highlighter-rouge">Helm client</code> 인증서 만들기</h2>
<ul>
<li>Helm client용 개인키를 생성한다.</li>
</ul>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>openssl genrsa <span class="nt">-out</span> ./helm.key.pem 4096
Generating RSA private key, 4096 bit long modulus
..................................++
......................................++
e is 65537 <span class="o">(</span>0x010001<span class="o">)</span>
</code></pre></div></div>
<ul>
<li>Helm client용 인증서를 생성한다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl req -key helm.key.pem -new -sha256 -out helm.csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:KR
State or Province Name (full name) [Some-State]:Gyeonggi-do
Locality Name (eg, city) []:Seongnam
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Helm Client
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:helm-client
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
</code></pre></div></div>
<ul>
<li>Helm client용 인증서를 CA의 인증서로 서명한다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in helm.csr.pem -out helm.cert.pem -days 365
Signature ok
subject=C = KR, ST = Gyeonggi-do, L = Seongnam, O = Helm Client, CN = helm-client
Getting CA Private Key
</code></pre></div></div>
<h2 id="서비스-어카운트-만들기">서비스 어카운트 만들기</h2>
<p>‘Tiller’가 사용할 <code class="language-plaintext highlighter-rouge">serviceaccount</code>를 생성하고, <code class="language-plaintext highlighter-rouge">cluster-admin</code> 클러스터롤(ClusterRole)을 바인딩해준다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: tiller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tiller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: tiller
namespace: kube-system
EOF
</code></pre></div></div>
<h2 id="tiller-설치하기"><code class="language-plaintext highlighter-rouge">Tiller</code> 설치하기</h2>
<p>생성한 인증서와 서비스어카운트를 지정하여, ‘Tiller’를 설치한다.</p>
<p>설치 명령어는 다음과 같다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>helm init <span class="nt">--service-account</span> tiller <span class="nt">--tiller-tls</span> <span class="nt">--tiller-tls-cert</span> ./tiller.cert.pem <span class="nt">--tiller-tls-key</span> ./tiller.key.pem <span class="nt">--tiller-tls-verify</span> <span class="nt">--tls-ca-cert</span> ca.cert.pem
</code></pre></div></div>
<p>설치가 완료되면, <code class="language-plaintext highlighter-rouge">helm ls</code>명령어를 실행해 본다. 다음과 같은 에러가 발생하면 정상적으로 설치한 것이다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>helm <span class="nb">ls
</span>Error: transport is closing
</code></pre></div></div>
<h2 id="helm-client-설정하기"><code class="language-plaintext highlighter-rouge">Helm client</code> 설정하기</h2>
<p>설치한 ‘Tiller’는 TLS 로 보호받고 있기 때문에, <code class="language-plaintext highlighter-rouge">helm</code> 클라이언트로 접근하려면 인증서를 지정해 줘야한다.</p>
<p>가장 간단한 방법은 인증서 정보를 모두 지정해 주는 것이다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>helm <span class="nb">ls</span> <span class="nt">--tls</span> <span class="nt">--tls-ca-cert</span> ca.cert.pem <span class="nt">--tls-cert</span> helm.cert.pem <span class="nt">--tls-key</span> helm.key.pem
</code></pre></div></div>
<p>매번 인증서를 지정해주는것은 불편하기 때문에, 인증서를 $HELM_HOME에 복사해 놓으면 좀 더 쉽게 사용할 수 있다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">export </span><span class="nv">HELM_HOME</span><span class="o">=</span>/Users/kangwoo/.helm
<span class="nv">$ </span><span class="nb">cp </span>ca.cert.pem <span class="nv">$HELM_HOME</span>/ca.pem
<span class="nv">$ </span><span class="nb">cp </span>helm.cert.pem <span class="nv">$HELM_HOME</span>/cert.pem
<span class="nv">$ </span><span class="nb">cp </span>helm.key.pem <span class="nv">$HELM_HOME</span>/key.pem
</code></pre></div></div>
<p>인증서를 $HELM_HOME에 복사하였다면, <code class="language-plaintext highlighter-rouge">helm</code>을 실행할때 <code class="language-plaintext highlighter-rouge">--tls</code>만 붙여주면 된다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>helm <span class="nb">ls</span> <span class="nt">--tls</span>
</code></pre></div></div>
<h2 id="kubeapps-설치하기">Kubeapps 설치하기</h2>
<p><code class="language-plaintext highlighter-rouge">helm</code>을 사용해서 <code class="language-plaintext highlighter-rouge">kubeapps</code>를 설치할 것이다.
<code class="language-plaintext highlighter-rouge">tiller</code>의 tls와 OIDC 인증을 위해서 <code class="language-plaintext highlighter-rouge">values.yaml</code>값을 수정해 준다.</p>
<ul>
<li>App Version: v1.5.0</li>
<li>Chart Version: 2.1.2</li>
</ul>
<h3 id="ingress-설절하기">ingress 설절하기</h3>
<p><code class="language-plaintext highlighter-rouge">ingress</code>를 사용하기 위해서 설정해준다.</p>
<p><code class="language-plaintext highlighter-rouge">ingress.enabled</code>를 <code class="language-plaintext highlighter-rouge">true</code>로 변경하고, <code class="language-plaintext highlighter-rouge">ingress.hosts.name</code>을 설정한다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">ingress</span><span class="pi">:</span>
<span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
<span class="nn">...</span>
<span class="na">hosts</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">kubeapps.xxx.com</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
</code></pre></div></div>
<h3 id="tillerproxy-tls-설정하기">tillerProxy tls 설정하기</h3>
<p><code class="language-plaintext highlighter-rouge">tillerProxy.tls.verify</code>을 <code class="language-plaintext highlighter-rouge">true</code>로 변경하고,
<code class="language-plaintext highlighter-rouge">tillerProxy.tls.ca</code>, <code class="language-plaintext highlighter-rouge">tillerProxy.tls.cert</code>, <code class="language-plaintext highlighter-rouge">tillerProxy.tls.key</code> 값을 설정한다.</p>
<ul>
<li>tillerProxy.tls.ca=”$(cat ca.cert.pem)”</li>
<li>tillerProxy.tls.cert=”$(cat helm.cert.pem)”</li>
<li>tillerProxy.tls.key=”$(cat helm.key.pem)”</li>
</ul>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">...</span>
<span class="na">tillerProxy</span><span class="pi">:</span>
<span class="na">replicaCount</span><span class="pi">:</span> <span class="m">2</span>
<span class="na">image</span><span class="pi">:</span>
<span class="na">registry</span><span class="pi">:</span> <span class="s">docker.io</span>
<span class="na">repository</span><span class="pi">:</span> <span class="s">bitnami/kubeapps-tiller-proxy</span>
<span class="na">tag</span><span class="pi">:</span> <span class="s">1.5.0-r0</span>
<span class="na">service</span><span class="pi">:</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">8080</span>
<span class="na">host</span><span class="pi">:</span> <span class="s">tiller-deploy.kube-system:44134</span>
<span class="na">tls</span><span class="pi">:</span>
<span class="na">ca</span><span class="pi">:</span> <span class="pi">|-</span>
<span class="s">-----BEGIN CERTIFICATE-----</span>
<span class="s">MIIF1zCCA7+gAwIBAgIJAPrXoUYpgyDEMA0GCSqGSIb3DQEBCwUAMIGBMQswCQYD</span>
<span class="s">...</span>
<span class="s">-----END CERTIFICATE-----</span>
<span class="na">cert</span><span class="pi">:</span> <span class="pi">|-</span>
<span class="s">-----BEGIN CERTIFICATE-----</span>
<span class="s">MIIFYDCCA0gCCQCnyMMmF4lKHzANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMC</span>
<span class="s">...</span>
<span class="s">-----END CERTIFICATE-----</span>
<span class="na">key</span><span class="pi">:</span> <span class="pi">|-</span>
<span class="s">-----BEGIN RSA PRIVATE KEY-----</span>
<span class="s">MIIJKgIBAAKCAgEA3Mb/4vvMqMVouSV2wLOX94R2okP0rcswLBUGR66asD1CLIa/</span>
<span class="s">...</span>
<span class="s">-----END RSA PRIVATE KEY-----</span>
<span class="na">verify</span><span class="pi">:</span> <span class="no">true</span>
<span class="nn">...</span>
</code></pre></div></div>
<h3 id="oidc-인증-활성화하기">OIDC 인증 활성화하기</h3>
<p>쿠버네티스 클러스터에서 사용하는 OIDC Provider를 <code class="language-plaintext highlighter-rouge">authProxy</code>에 설정해준다.
그래야 <code class="language-plaintext highlighter-rouge">kubeapps</code> 웹 UI 화면에 접속할 때, 로그인을 할 수 있고 해당 토큰으로 <code class="language-plaintext highlighter-rouge">kubeapps</code>를 사용할 수 있다.</p>
<p><code class="language-plaintext highlighter-rouge">authProxy.enabled</code>을 <code class="language-plaintext highlighter-rouge">true</code>로 변경하고,
<code class="language-plaintext highlighter-rouge">authProxy.discoveryURL</code>, <code class="language-plaintext highlighter-rouge">authProxy.clientID</code>, <code class="language-plaintext highlighter-rouge">authProxy.clientSecret</code>의 값을 설정한 후,
<code class="language-plaintext highlighter-rouge">authProxy.additionalFlags</code>에 <code class="language-plaintext highlighter-rouge">--secure-cookie=false</code>, <code class="language-plaintext highlighter-rouge">--scopes=openid groups email</code>을 추가해 준다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">...</span>
<span class="na">authProxy</span><span class="pi">:</span>
<span class="c1"># Set to true to enable the OIDC proxy</span>
<span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
<span class="c1"># Image used for the proxy</span>
<span class="na">image</span><span class="pi">:</span>
<span class="na">registry</span><span class="pi">:</span> <span class="s">docker.io</span>
<span class="na">repository</span><span class="pi">:</span> <span class="s">bitnami/keycloak-gatekeeper</span>
<span class="na">tag</span><span class="pi">:</span> <span class="s">2.3.0-r1</span>
<span class="c1"># Mandatory parametes</span>
<span class="na">discoveryURL</span><span class="pi">:</span> <span class="s">https://REPLACE_URL</span>
<span class="na">clientID</span><span class="pi">:</span> <span class="s">REPLACE_CLIENT_ID</span>
<span class="na">clientSecret</span><span class="pi">:</span> <span class="s">REPLACE_CLIENT_SECRET</span>
<span class="c1"># Additional flags for Keycloak-Gatekeeper</span>
<span class="na">additionalFlags</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">--secure-cookie=false</span>
<span class="pi">-</span> <span class="s">--scopes=openid groups email</span>
</code></pre></div></div>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">$ helm install -f values.yaml bitnami/kubeapps \</span>
<span class="s">--namespace kubeapps --name kubeapps \</span>
<span class="s">--tls</span>
<span class="na">NAME</span><span class="pi">:</span> <span class="s">kubeapps</span>
<span class="na">LAST DEPLOYED</span><span class="pi">:</span> <span class="s">Tue Sep 10 19:45:04 </span><span class="m">2019</span>
<span class="na">NAMESPACE</span><span class="pi">:</span> <span class="s">kubeapps</span>
<span class="na">STATUS</span><span class="pi">:</span> <span class="s">DEPLOYED</span>
<span class="na">RESOURCES</span><span class="pi">:</span>
<span class="s">==> v1/ConfigMap</span>
<span class="s">NAME DATA AGE</span>
<span class="s">kubeapps-frontend-config 1 1s</span>
<span class="s">kubeapps-internal-dashboard-config 2 1s</span>
<span class="s">==> v1/Pod(related)</span>
<span class="s">NAME READY STATUS RESTARTS AGE</span>
<span class="s">kubeapps-86cd959cc8-knbwk 0/2 ContainerCreating 0 1s</span>
<span class="s">kubeapps-86cd959cc8-mtgqc 0/2 Pending 0 1s</span>
<span class="s">kubeapps-internal-apprepository-controller-77cc98bcc-8s5dv 0/1 ContainerCreating 0 1s</span>
<span class="s">kubeapps-internal-chartsvc-7fc7bc4fc5-4ssdx 0/1 ContainerCreating 0 1s</span>
<span class="s">kubeapps-internal-chartsvc-7fc7bc4fc5-n4x65 0/1 ContainerCreating 0 1s</span>
<span class="s">kubeapps-internal-dashboard-5df4c549b9-dckw2 0/1 Pending 0 1s</span>
<span class="s">kubeapps-internal-dashboard-5df4c549b9-qlvjl 0/1 ContainerCreating 0 1s</span>
<span class="s">kubeapps-internal-tiller-proxy-68c5cb8998-fnfbg 0/1 Pending 0 1s</span>
<span class="s">kubeapps-internal-tiller-proxy-68c5cb8998-rgc6x 0/1 ContainerCreating 0 1s</span>
<span class="s">kubeapps-mongodb-85f58746ff-d6p5g 0/1 ContainerCreating 0 1s</span>
<span class="s">==> v1/Secret</span>
<span class="s">NAME TYPE DATA AGE</span>
<span class="s">kubeapps-internal-tiller-proxy Opaque 3 1s</span>
<span class="s">==> v1/Service</span>
<span class="s">NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE</span>
<span class="s">kubeapps ClusterIP 172.30.67.115 <none> 80/TCP 1s</span>
<span class="s">kubeapps-internal-chartsvc ClusterIP 172.31.216.84 <none> 8080/TCP 1s</span>
<span class="s">kubeapps-internal-dashboard ClusterIP 172.30.205.251 <none> 8080/TCP 1s</span>
<span class="s">kubeapps-internal-tiller-proxy ClusterIP 172.31.13.240 <none> 8080/TCP 1s</span>
<span class="s">kubeapps-mongodb ClusterIP 172.30.33.149 <none> 27017/TCP 1s</span>
<span class="s">==> v1/ServiceAccount</span>
<span class="s">NAME SECRETS AGE</span>
<span class="s">kubeapps-internal-apprepository-controller 1 1s</span>
<span class="s">kubeapps-internal-tiller-proxy 1 1s</span>
<span class="s">==> v1beta1/Deployment</span>
<span class="s">NAME READY UP-TO-DATE AVAILABLE AGE</span>
<span class="s">kubeapps-mongodb 0/1 1 0 1s</span>
<span class="s">==> v1beta1/Ingress</span>
<span class="s">NAME HOSTS ADDRESS PORTS AGE</span>
<span class="s">kubeapps kubeapps.xxx.com 80 1s</span>
<span class="s">==> v1beta1/Role</span>
<span class="s">NAME AGE</span>
<span class="s">kubeapps-internal-apprepository-controller 1s</span>
<span class="s">kubeapps-internal-tiller-proxy 1s</span>
<span class="s">kubeapps-repositories-read 1s</span>
<span class="s">kubeapps-repositories-write 1s</span>
<span class="s">==> v1beta1/RoleBinding</span>
<span class="s">NAME AGE</span>
<span class="s">kubeapps-internal-apprepository-controller 1s</span>
<span class="s">kubeapps-internal-tiller-proxy 1s</span>
<span class="s">==> v1beta2/Deployment</span>
<span class="s">NAME READY UP-TO-DATE AVAILABLE AGE</span>
<span class="s">kubeapps 0/2 2 0 1s</span>
<span class="s">kubeapps-internal-apprepository-controller 0/1 1 0 1s</span>
<span class="s">kubeapps-internal-chartsvc 0/2 2 0 1s</span>
<span class="s">kubeapps-internal-dashboard 0/2 2 0 1s</span>
<span class="s">kubeapps-internal-tiller-proxy 0/2 2 0 1s</span>
<span class="na">NOTES</span><span class="pi">:</span>
<span class="err">**</span> <span class="s">Please be patient while the chart is being deployed **</span>
<span class="na">Tip</span><span class="pi">:</span>
<span class="na">Watch the deployment status using the command</span><span class="pi">:</span> <span class="s">kubectl get pods -w --namespace kubeapps</span>
<span class="na">Kubeapps can be accessed via port 80 on the following DNS name from within your cluster</span><span class="pi">:</span>
<span class="s">kubeapps.kubeapps.svc.cluster.local</span>
<span class="s">To access Kubeapps from outside your K8s cluster, follow the steps below</span><span class="pi">:</span>
<span class="s">1. Get the Kubeapps URL and associate Kubeapps hostname to your cluster external IP</span><span class="pi">:</span>
<span class="s">export CLUSTER_IP=$(minikube ip)</span> <span class="c1"># On Minikube. Use: `kubectl cluster-info` on others K8s clusters</span>
<span class="s">echo "Kubeapps URL</span><span class="pi">:</span> <span class="s">http://kubeapps.xxx.com/"</span>
<span class="s">echo "$CLUSTER_IP kubeapps.xxx.com" | sudo tee -a /etc/hosts</span>
<span class="s">2. Open a browser and access Kubeapps using the obtained URL.</span>
</code></pre></div></div>
<p>ingress에 설정한 주소로 접속하면 <code class="language-plaintext highlighter-rouge">kubeapps</code>를 사용할 수 있다.</p>
<h1 id="참고-문서">참고 문서</h1>
<ul>
<li><a href="https://helm.sh/docs/">https://helm.sh/docs/</a></li>
<li><a href="https://github.com/bitnami/charts/tree/master/bitnami/kubeapps">https://github.com/bitnami/charts/tree/master/bitnami/kubeapps</a></li>
</ul>강우kangwoo@gmail.comHelm Helm은 쿠버네티스 패키지 관리 툴이다. chart라고 부르는, 이미 만들어 놓은 패키지 명세서를 이용해서 손쉽게 애플리케이션을 배포하고 관리할 수 있다.쿠버네티스 네임스페이스가 삭제되지 않을 때 강제 삭제하기2019-09-09T11:17:00+00:002019-09-09T11:17:00+00:00https://kangwoo.github.io/devops/kubernetes/delete-namespace-stuck-at-terminating-v2<h1 id="문제">문제</h1>
<p>가끔식 문제가 발생하여, 네임스페이스(namespace)를 삭제할때, 상태만 <code class="language-plaintext highlighter-rouge">Terminating</code>으로 변하고, 계속 기다려도 삭제가 되지 않는 경우가 있다.</p>
<p>이럴 경우에는 네임스페이스의 <code class="language-plaintext highlighter-rouge">finalizers</code>를 제거해 주면 된다. (하지만 정상작으로 삭제될때까지 기다리는게 가장 좋다)</p>
<h1 id="해결-방법">해결 방법</h1>
<p><code class="language-plaintext highlighter-rouge">foo</code>라는 네임스페이스가 있다고 가정한다.</p>
<p>다음과 같은 명령어로 네임스페이스 정의 내역을 json 파일로 저장한다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl get namespace foo <span class="nt">-o</span> json <span class="o">></span> foo.json
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">foo.json</code> 파일을 영어서 <code class="language-plaintext highlighter-rouge">finalizers</code> 부분에 있는 <code class="language-plaintext highlighter-rouge">kubernetes</code> 값을 삭제하고, 저장한다.</p>
<p>그런 다음 쿠베 프락시를 실행한다. 쿠버네티스 api를 호출할 예정인데, 인증 토큰이 필요하다.
<code class="language-plaintext highlighter-rouge">kubectl proxy</code>를 이용하면, 저장되어 있는 인증토큰을 자동으로 이용한다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl proxy
Starting to serve on 127.0.0.1:8001
</code></pre></div></div>
<p>다른 터미널을 열어서 쿠버네티스 api를 호출한다. 다음과 같이 api를 호출하면 변경된 <code class="language-plaintext highlighter-rouge">finalizers</code> 부분이 쿠버네티스에 반영된다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-k</span> <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="nt">-X</span> PUT <span class="nt">--data-binary</span> @foo.json http://127.0.0.1:8001/api/v1/namespaces/foo/finalize
</code></pre></div></div>강우kangwoo@gmail.com문제 가끔식 문제가 발생하여, 네임스페이스(namespace)를 삭제할때, 상태만 Terminating으로 변하고, 계속 기다려도 삭제가 되지 않는 경우가 있다.파이썬 requirements.txt2019-09-09T11:17:00+00:002019-09-09T11:17:00+00:00https://kangwoo.github.io/devops/python/python-requirements<p>파이썬 환경에서 자신이 사용하고자 하는 파이썬 패키지를 일일히 설치해주는것은 불편한 일이다.
<code class="language-plaintext highlighter-rouge">requirements.txt</code>을 사용하면 손쉽게 패키지를 설치할 수 있다.</p>
<h2 id="requirementstxt-만들기"><code class="language-plaintext highlighter-rouge">requirements.txt</code> 만들기</h2>
<p>자신의 구성 환경을 <code class="language-plaintext highlighter-rouge">requirements.txt</code>로 만들려면, 다음과 같은 명령어를 실행한다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip freeze <span class="o">></span> requirements.txt
</code></pre></div></div>
<p>사용자 디렉토리에 설치된 패키지만을 가져오로면 <code class="language-plaintext highlighter-rouge">--user</code> 옵션을 사용하면 된다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip freeze <span class="nt">--user</span> <span class="o">></span> requirements.txt
</code></pre></div></div>
<h2 id="requirementstxt로-패키지-설치"><code class="language-plaintext highlighter-rouge">requirements.txt</code>로 패키지 설치</h2>
<p><code class="language-plaintext highlighter-rouge">requirements.txt</code>로 지정한 환경에 패키치를 설치하려면, 다음과 같은 명령어를 실행한다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install</span> <span class="nt">-r</span> requirements.txt
</code></pre></div></div>강우kangwoo@gmail.com파이썬 환경에서 자신이 사용하고자 하는 파이썬 패키지를 일일히 설치해주는것은 불편한 일이다. requirements.txt을 사용하면 손쉽게 패키지를 설치할 수 있다.Prometheus를 사용해서 NVIDIA GPU 모니터링 하기2019-09-08T06:13:00+00:002019-09-08T06:13:00+00:00https://kangwoo.github.io/devops/kubernetes/nvidia-gpu-monitoring<h1 id="node의-gpu-모니터링-하기">Node의 GPU 모니터링 하기</h1>
<p><code class="language-plaintext highlighter-rouge">prometheus</code>를 사용해서 노드들의 매트틱을 수집하고 있다면, 아마 <code class="language-plaintext highlighter-rouge">node-exporter</code>를 사용하고 있을 것이다.
<code class="language-plaintext highlighter-rouge">NVIDIA</code>에서는 <code class="language-plaintext highlighter-rouge">dcgm-exporter</code>라는 GPU 매트릭 출력용 이미지를 제공하고 있다.
이 <code class="language-plaintext highlighter-rouge">dcgm-exporter</code>과 <code class="language-plaintext highlighter-rouge">node-exporter</code>를 결합하여 사용하면, GPU 매트릭을 수집할 수 있다.</p>
<h3 id="dcgm-exporter">dcgm-exporter</h3>
<p><code class="language-plaintext highlighter-rouge">dcgm(Data Center GPU Manager) exporter</code>는 <code class="language-plaintext highlighter-rouge">nv-hostenging</code>을 시작해서,
매초마다 GPU 매트릭을 읽어서 <code class="language-plaintext highlighter-rouge">prometheus</code> 형식으로 출력해주는 간단한 쉘 스크립트이다.</p>
<h3 id="node-설정하기">Node 설정하기</h3>
<p>우선 일반 노드와 GPU 노드를 분리하기 위해서 <code class="language-plaintext highlighter-rouge">taint</code>와 <code class="language-plaintext highlighter-rouge">label</code>을 설정해주었다.
대부분 <code class="language-plaintext highlighter-rouge">node-exporter</code>를 실행하기 위해서 <code class="language-plaintext highlighter-rouge">DaemonSet</code>을 사용했을 것이다.</p>
<p>일반 노드에서는 <code class="language-plaintext highlighter-rouge">node-exporter</code>만을 실행하기 위해서 <code class="language-plaintext highlighter-rouge">taint nvidia.com/gpu=:NoSchedule</code>를 사용하였고,
GPU 노드에서는 <code class="language-plaintext highlighter-rouge">node-exporter</code> + <code class="language-plaintext highlighter-rouge">dcgm-exporter</code>를 실행하기 위해서 <code class="language-plaintext highlighter-rouge">label hardware-type=NVIDIAGPU</code>를 사용하였다.</p>
<p><code class="language-plaintext highlighter-rouge">nvidia.com/brand</code>는 현재로는 별의미가 없지만 붙여주었다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl taint nodes <span class="k">${</span><span class="nv">node</span><span class="k">}</span> nvidia.com/gpu<span class="o">=</span>:NoSchedule
kubectl label nodes <span class="k">${</span><span class="nv">node</span><span class="k">}</span> <span class="s2">"nvidia.com/brand=</span><span class="k">${</span><span class="nv">label</span><span class="k">}</span><span class="s2">"</span>
kubectl label nodes <span class="k">${</span><span class="nv">node</span><span class="k">}</span> hardware-type<span class="o">=</span>NVIDIAGPU
</code></pre></div></div>
<h1 id="기존-node-exporter에-dcgm-exporter-추가하기">기존 <code class="language-plaintext highlighter-rouge">node-exporter</code>에 <code class="language-plaintext highlighter-rouge">dcgm-exporter</code> 추가하기</h1>
<p><code class="language-plaintext highlighter-rouge">dcgm-exporter</code>가 GPU 매트릭을 파일로 남기고, <code class="language-plaintext highlighter-rouge">prometheus</code>는 그 파일을 읽어서 GPU 매트릭을 같이 출력한다.</p>
<h3 id="gpu-노드용">GPU 노드용</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app.kubernetes.io/name: node-exporter
app.kubernetes.io/instance: gpu-node-exporter
app.kubernetes.io/part-of: prometheus
app.kubernetes.io/managed-by: argo-system
name: prometheus-gpu-node-exporter
namespace: argo-system
spec:
revisionHistoryLimit: 10
selector:
matchLabels:
app.kubernetes.io/name: node-exporter
app.kubernetes.io/instance: gpu-node-exporter
app.kubernetes.io/part-of: prometheus
app.kubernetes.io/managed-by: argo-system
template:
metadata:
labels:
app.kubernetes.io/name: node-exporter
app.kubernetes.io/instance: gpu-node-exporter
app.kubernetes.io/part-of: prometheus
app.kubernetes.io/managed-by: argo-system
spec:
nodeSelector:
hardware-type: NVIDIAGPU
containers:
- args:
- <span class="nt">--path</span>.procfs<span class="o">=</span>/host/proc
- <span class="nt">--path</span>.sysfs<span class="o">=</span>/host/sys
- <span class="s2">"--collector.textfile.directory=/run/prometheus"</span>
image: prom/node-exporter:v0.18.1
imagePullPolicy: IfNotPresent
name: prometheus-node-exporter
ports:
- containerPort: 9100
hostPort: 9100
name: metrics
protocol: TCP
resources:
limits:
cpu: 500m
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /host/proc
name: proc
readOnly: <span class="nb">true</span>
- mountPath: /host/sys
name: sys
readOnly: <span class="nb">true</span>
- name: collector-textfiles
readOnly: <span class="nb">true
</span>mountPath: /run/prometheus
- image: nvidia/dcgm-exporter:1.4.6
name: nvidia-dcgm-exporter
securityContext:
runAsNonRoot: <span class="nb">false
</span>runAsUser: 0
volumeMounts:
- name: collector-textfiles
mountPath: /run/prometheus
dnsPolicy: ClusterFirst
hostNetwork: <span class="nb">true
</span>hostPID: <span class="nb">true
</span>restartPolicy: Always
serviceAccount: prometheus-node-exporter
serviceAccountName: prometheus-node-exporter
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/master
- effect: NoSchedule
key: node-role.kubernetes.io/ingress
operator: Exists
- effect: NoSchedule
key: nvidia.com/gpu
operator: Exists
volumes:
- hostPath:
path: /proc
<span class="nb">type</span>: <span class="s2">""</span>
name: proc
- hostPath:
path: /sys
<span class="nb">type</span>: <span class="s2">""</span>
name: sys
- name: collector-textfiles
emptyDir:
medium: Memory
- name: pod-gpu-resources
hostPath:
path: /var/lib/kubelet/pod-resources
updateStrategy:
<span class="nb">type</span>: OnDelete
</code></pre></div></div>
<h3 id="일반-노드">일반 노드</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app.kubernetes.io/name: node-exporter
app.kubernetes.io/instance: node-exporter
app.kubernetes.io/part-of: prometheus
app.kubernetes.io/managed-by: argo-system
name: prometheus-node-exporter
namespace: argo-system
spec:
revisionHistoryLimit: 10
selector:
matchLabels:
app.kubernetes.io/name: node-exporter
app.kubernetes.io/instance: node-exporter
app.kubernetes.io/part-of: prometheus
app.kubernetes.io/managed-by: argo-system
template:
metadata:
labels:
app.kubernetes.io/name: node-exporter
app.kubernetes.io/instance: node-exporter
app.kubernetes.io/part-of: prometheus
app.kubernetes.io/managed-by: argo-system
spec:
containers:
- args:
- <span class="nt">--path</span>.procfs<span class="o">=</span>/host/proc
- <span class="nt">--path</span>.sysfs<span class="o">=</span>/host/sys
image: prom/node-exporter:v0.18.1
imagePullPolicy: IfNotPresent
name: prometheus-node-exporter
ports:
- containerPort: 9100
hostPort: 9100
name: metrics
protocol: TCP
resources:
limits:
cpu: 500m
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /host/proc
name: proc
readOnly: <span class="nb">true</span>
- mountPath: /host/sys
name: sys
readOnly: <span class="nb">true
</span>dnsPolicy: ClusterFirst
hostNetwork: <span class="nb">true
</span>hostPID: <span class="nb">true
</span>restartPolicy: Always
serviceAccount: prometheus-node-exporter
serviceAccountName: prometheus-node-exporter
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/master
- effect: NoSchedule
key: node-role.kubernetes.io/ingress
operator: Exists
volumes:
- hostPath:
path: /proc
<span class="nb">type</span>: <span class="s2">""</span>
name: proc
- hostPath:
path: /sys
<span class="nb">type</span>: <span class="s2">""</span>
name: sys
updateStrategy:
<span class="nb">type</span>: OnDelete
</code></pre></div></div>
<h1 id="참고-문서">참고 문서</h1>
<ul>
<li><a href="https://docs.nvidia.com/datacenter/kubernetes/kubernetes-upstream/index.html#kubernetes-gpu-monitoring-agent">https://docs.nvidia.com/datacenter/kubernetes/kubernetes-upstream/index.html#kubernetes-gpu-monitoring-agent</a></li>
<li><a href="https://github.com/NVIDIA/gpu-monitoring-tools">https://github.com/NVIDIA/gpu-monitoring-tools</a></li>
</ul>강우kangwoo@gmail.comNode의 GPU 모니터링 하기 prometheus를 사용해서 노드들의 매트틱을 수집하고 있다면, 아마 node-exporter를 사용하고 있을 것이다. NVIDIA에서는 dcgm-exporter라는 GPU 매트릭 출력용 이미지를 제공하고 있다. 이 dcgm-exporter과 node-exporter를 결합하여 사용하면, GPU 매트릭을 수집할 수 있다.Keras GPU 사용하기2019-08-07T23:50:12+00:002019-08-07T23:50:12+00:00https://kangwoo.github.io/data%20science/machine%20learning/tensorflow/keras/keras-gpu<p>쿠버네티스(kubernetes) 위에서 GPU 4개를 할당한 Jupyter를 사용하고 있다.
Jupyter Notebook 에서 텐서플로-케라스를 사용하고 있는데, 노트북을 1개 더 생성해서 작업 할 경우 OOM 에러가 발생하였다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ResourceExhaustedError <span class="o">(</span>see above <span class="k">for </span>traceback<span class="o">)</span>: OOM when allocating tensor with shape[3,3,128,128] and <span class="nb">type </span>float on /job:localhost/replica:0/task:0/device:GPU:0 by allocator GPU_0_bfc
<span class="o">[[</span>node conv2d_3/kernel/Initializer/random_uniform/RandomUniform <span class="o">(</span>defined at <ipython-input-2-355421ac90ac>:1<span class="o">)</span> <span class="o">]]</span>
Hint: If you want to see a list of allocated tensors when OOM happens, add report_tensor_allocations_upon_oom to RunOptions <span class="k">for </span>current allocation info.
</code></pre></div></div>
<p>양쪽 노트북에서 모드 GPU:0을 사용해서 생긴 문제인거 같다.
첫번째 노트북에서 GPU:0의 메모리를 거의 풀로 사용해버린 상태에서, 두번째 노트북에서 GPU:0을 사용하려니 메모리가 부족한 상태가 발생한것이다.</p>
<p>서버에서 <code class="language-plaintext highlighter-rouge">nvidia-smi</code>을 실행해보면, 프로세스는 생성되었지만, 할당된 메모리가 작다는것을 알 수 있다.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Every</span> <span class="mf">2.0</span><span class="n">s</span><span class="p">:</span> <span class="n">nvidia</span><span class="o">-</span><span class="n">smi</span> <span class="n">Thu</span> <span class="n">Aug</span> <span class="mi">8</span> <span class="mi">10</span><span class="p">:</span><span class="mi">09</span><span class="p">:</span><span class="mi">46</span> <span class="mi">2019</span>
<span class="n">Thu</span> <span class="n">Aug</span> <span class="mi">8</span> <span class="mi">08</span><span class="p">:</span><span class="mi">09</span><span class="p">:</span><span class="mi">46</span> <span class="mi">2019</span>
<span class="o">+-----------------------------------------------------------------------------+</span>
<span class="o">|</span> <span class="n">NVIDIA</span><span class="o">-</span><span class="n">SMI</span> <span class="mf">418.67</span> <span class="n">Driver</span> <span class="n">Version</span><span class="p">:</span> <span class="mf">418.67</span> <span class="n">CUDA</span> <span class="n">Version</span><span class="p">:</span> <span class="mf">10.1</span> <span class="o">|</span>
<span class="o">|-------------------------------+----------------------+----------------------+</span>
<span class="o">|</span> <span class="n">GPU</span> <span class="n">Name</span> <span class="n">Persistence</span><span class="o">-</span><span class="n">M</span><span class="o">|</span> <span class="n">Bus</span><span class="o">-</span><span class="n">Id</span> <span class="n">Disp</span><span class="o">.</span><span class="n">A</span> <span class="o">|</span> <span class="n">Volatile</span> <span class="n">Uncorr</span><span class="o">.</span> <span class="n">ECC</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">Fan</span> <span class="n">Temp</span> <span class="n">Perf</span> <span class="n">Pwr</span><span class="p">:</span><span class="n">Usage</span><span class="o">/</span><span class="n">Cap</span><span class="o">|</span> <span class="n">Memory</span><span class="o">-</span><span class="n">Usage</span> <span class="o">|</span> <span class="n">GPU</span><span class="o">-</span><span class="n">Util</span> <span class="n">Compute</span> <span class="n">M</span><span class="o">.</span> <span class="o">|</span>
<span class="o">|===============================+======================+======================|</span>
<span class="o">|</span> <span class="mi">0</span> <span class="n">Tesla</span> <span class="n">M40</span> <span class="n">Off</span> <span class="o">|</span> <span class="mi">00000000</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mf">00.0</span> <span class="n">Off</span> <span class="o">|</span> <span class="mi">0</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">N</span><span class="o">/</span><span class="n">A</span> <span class="mi">35</span><span class="n">C</span> <span class="n">P0</span> <span class="mi">63</span><span class="n">W</span> <span class="o">/</span> <span class="mi">250</span><span class="n">W</span> <span class="o">|</span> <span class="mi">11302</span><span class="n">MiB</span> <span class="o">/</span> <span class="mi">11448</span><span class="n">MiB</span> <span class="o">|</span> <span class="mi">0</span><span class="o">%</span> <span class="n">Default</span> <span class="o">|</span>
<span class="o">+-------------------------------+----------------------+----------------------+</span>
<span class="o">|</span> <span class="mi">1</span> <span class="n">Tesla</span> <span class="n">M40</span> <span class="n">Off</span> <span class="o">|</span> <span class="mi">00000000</span><span class="p">:</span><span class="mi">82</span><span class="p">:</span><span class="mf">00.0</span> <span class="n">Off</span> <span class="o">|</span> <span class="mi">0</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">N</span><span class="o">/</span><span class="n">A</span> <span class="mi">37</span><span class="n">C</span> <span class="n">P0</span> <span class="mi">62</span><span class="n">W</span> <span class="o">/</span> <span class="mi">250</span><span class="n">W</span> <span class="o">|</span> <span class="mi">212</span><span class="n">MiB</span> <span class="o">/</span> <span class="mi">11448</span><span class="n">MiB</span> <span class="o">|</span> <span class="mi">0</span><span class="o">%</span> <span class="n">Default</span> <span class="o">|</span>
<span class="o">+-------------------------------+----------------------+----------------------+</span>
<span class="o">|</span> <span class="mi">2</span> <span class="n">Tesla</span> <span class="n">M40</span> <span class="n">Off</span> <span class="o">|</span> <span class="mi">00000000</span><span class="p">:</span><span class="mi">85</span><span class="p">:</span><span class="mf">00.0</span> <span class="n">Off</span> <span class="o">|</span> <span class="mi">0</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">N</span><span class="o">/</span><span class="n">A</span> <span class="mi">35</span><span class="n">C</span> <span class="n">P0</span> <span class="mi">62</span><span class="n">W</span> <span class="o">/</span> <span class="mi">250</span><span class="n">W</span> <span class="o">|</span> <span class="mi">212</span><span class="n">MiB</span> <span class="o">/</span> <span class="mi">11448</span><span class="n">MiB</span> <span class="o">|</span> <span class="mi">0</span><span class="o">%</span> <span class="n">Default</span> <span class="o">|</span>
<span class="o">+-------------------------------+----------------------+----------------------+</span>
<span class="o">|</span> <span class="mi">3</span> <span class="n">Tesla</span> <span class="n">M40</span> <span class="n">Off</span> <span class="o">|</span> <span class="mi">00000000</span><span class="p">:</span><span class="mi">86</span><span class="p">:</span><span class="mf">00.0</span> <span class="n">Off</span> <span class="o">|</span> <span class="mi">0</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">N</span><span class="o">/</span><span class="n">A</span> <span class="mi">37</span><span class="n">C</span> <span class="n">P0</span> <span class="mi">62</span><span class="n">W</span> <span class="o">/</span> <span class="mi">250</span><span class="n">W</span> <span class="o">|</span> <span class="mi">212</span><span class="n">MiB</span> <span class="o">/</span> <span class="mi">11448</span><span class="n">MiB</span> <span class="o">|</span> <span class="mi">0</span><span class="o">%</span> <span class="n">Default</span> <span class="o">|</span>
<span class="o">+-------------------------------+----------------------+----------------------+</span>
<span class="o">+-----------------------------------------------------------------------------+</span>
<span class="o">|</span> <span class="n">Processes</span><span class="p">:</span> <span class="n">GPU</span> <span class="n">Memory</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">GPU</span> <span class="n">PID</span> <span class="n">Type</span> <span class="n">Process</span> <span class="n">name</span> <span class="n">Usage</span> <span class="o">|</span>
<span class="o">|=============================================================================|</span>
<span class="o">|</span> <span class="mi">0</span> <span class="mi">81475</span> <span class="n">C</span> <span class="o">/</span><span class="n">opt</span><span class="o">/</span><span class="n">conda</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python</span> <span class="mi">11060</span><span class="n">MiB</span> <span class="o">|</span>
<span class="o">|</span> <span class="mi">0</span> <span class="mi">93847</span> <span class="n">C</span> <span class="o">/</span><span class="n">opt</span><span class="o">/</span><span class="n">conda</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python</span> <span class="mi">229</span><span class="n">MiB</span> <span class="o">|</span>
<span class="o">|</span> <span class="mi">1</span> <span class="mi">81475</span> <span class="n">C</span> <span class="o">/</span><span class="n">opt</span><span class="o">/</span><span class="n">conda</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python</span> <span class="mi">99</span><span class="n">MiB</span> <span class="o">|</span>
<span class="o">|</span> <span class="mi">1</span> <span class="mi">93847</span> <span class="n">C</span> <span class="o">/</span><span class="n">opt</span><span class="o">/</span><span class="n">conda</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python</span> <span class="mi">99</span><span class="n">MiB</span> <span class="o">|</span>
<span class="o">|</span> <span class="mi">2</span> <span class="mi">81475</span> <span class="n">C</span> <span class="o">/</span><span class="n">opt</span><span class="o">/</span><span class="n">conda</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python</span> <span class="mi">99</span><span class="n">MiB</span> <span class="o">|</span>
<span class="o">|</span> <span class="mi">2</span> <span class="mi">93847</span> <span class="n">C</span> <span class="o">/</span><span class="n">opt</span><span class="o">/</span><span class="n">conda</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python</span> <span class="mi">99</span><span class="n">MiB</span> <span class="o">|</span>
<span class="o">|</span> <span class="mi">3</span> <span class="mi">81475</span> <span class="n">C</span> <span class="o">/</span><span class="n">opt</span><span class="o">/</span><span class="n">conda</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python</span> <span class="mi">99</span><span class="n">MiB</span> <span class="o">|</span>
<span class="o">|</span> <span class="mi">3</span> <span class="mi">93847</span> <span class="n">C</span> <span class="o">/</span><span class="n">opt</span><span class="o">/</span><span class="n">conda</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python</span> <span class="mi">99</span><span class="n">MiB</span> <span class="o">|</span>
<span class="o">+-----------------------------------------------------------------------------+</span>
</code></pre></div></div>
<p>알아서 노는 GPU를 사용하면 좋으련만… 어쩔수 없이 GPU 디바이스를 직접 지정하여서 사용핬다.</p>
<h3 id="디바이스-목록-보기">디바이스 목록 보기</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">tensorflow.python.client</span> <span class="kn">import</span> <span class="n">device_lib</span>
<span class="k">print</span><span class="p">(</span><span class="n">device_lib</span><span class="o">.</span><span class="n">list_local_devices</span><span class="p">())</span>
</code></pre></div></div>
<h3 id="특정-디바이스를-사용해서-모델-처리하기">특정 디바이스를 사용해서 모델 처리하기</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">tensorflow</span> <span class="k">as</span> <span class="n">tf</span>
<span class="k">with</span> <span class="n">tf</span><span class="o">.</span><span class="n">device</span><span class="p">(</span><span class="s">'/gpu:0'</span><span class="p">):</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">keras</span><span class="o">.</span><span class="n">models</span><span class="o">.</span><span class="n">Sequential</span><span class="p">()</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">Conv2D</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="n">activation</span><span class="o">=</span><span class="s">'relu'</span><span class="p">,</span> <span class="n">input_shape</span><span class="o">=</span><span class="p">(</span><span class="mi">150</span><span class="p">,</span> <span class="mi">150</span><span class="p">,</span> <span class="mi">3</span><span class="p">)))</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">MaxPool2D</span><span class="p">((</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">)))</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">Conv2D</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="n">activation</span><span class="o">=</span><span class="s">'relu'</span><span class="p">))</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">MaxPool2D</span><span class="p">((</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">)))</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">Conv2D</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="n">activation</span><span class="o">=</span><span class="s">'relu'</span><span class="p">))</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">MaxPool2D</span><span class="p">((</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">)))</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">Conv2D</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="n">activation</span><span class="o">=</span><span class="s">'relu'</span><span class="p">))</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">MaxPool2D</span><span class="p">((</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">)))</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">Flatten</span><span class="p">())</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">Dense</span><span class="p">(</span><span class="mi">512</span><span class="p">,</span> <span class="n">activation</span><span class="o">=</span><span class="s">'relu'</span><span class="p">))</span>
<span class="n">model</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">keras</span><span class="o">.</span><span class="n">layers</span><span class="o">.</span><span class="n">Dense</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">activation</span><span class="o">=</span><span class="s">'sigmoid'</span><span class="p">))</span>
<span class="n">model</span><span class="o">.</span><span class="nb">compile</span><span class="p">(</span><span class="n">loss</span><span class="o">=</span><span class="s">'binary_crossentropy'</span><span class="p">,</span> <span class="n">optimizer</span><span class="o">=</span><span class="n">keras</span><span class="o">.</span><span class="n">optimizers</span><span class="o">.</span><span class="n">RMSprop</span><span class="p">(</span><span class="n">lr</span><span class="o">=</span><span class="mf">1e-4</span><span class="p">),</span> <span class="n">metrics</span><span class="o">=</span><span class="p">[</span><span class="s">'acc'</span><span class="p">])</span>
<span class="n">train_datagen</span> <span class="o">=</span> <span class="n">keras</span><span class="o">.</span><span class="n">preprocessing</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">ImageDataGenerator</span><span class="p">(</span><span class="n">rescale</span><span class="o">=</span><span class="mf">1.</span><span class="o">/</span><span class="mi">255</span><span class="p">)</span>
<span class="n">validation_datagen</span> <span class="o">=</span> <span class="n">keras</span><span class="o">.</span><span class="n">preprocessing</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">ImageDataGenerator</span><span class="p">(</span><span class="n">rescale</span><span class="o">=</span><span class="mf">1.</span><span class="o">/</span><span class="mi">255</span><span class="p">)</span>
<span class="n">train_generator</span> <span class="o">=</span> <span class="n">train_datagen</span><span class="o">.</span><span class="n">flow_from_directory</span><span class="p">(</span><span class="n">train_dir</span><span class="p">,</span> <span class="n">target_size</span><span class="o">=</span><span class="p">(</span><span class="mi">150</span><span class="p">,</span> <span class="mi">150</span><span class="p">),</span> <span class="n">batch_size</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">class_mode</span><span class="o">=</span><span class="s">'binary'</span><span class="p">)</span>
<span class="n">validation_generator</span> <span class="o">=</span> <span class="n">validation_datagen</span><span class="o">.</span><span class="n">flow_from_directory</span><span class="p">(</span><span class="n">validation_dir</span><span class="p">,</span> <span class="n">target_size</span><span class="o">=</span><span class="p">(</span><span class="mi">150</span><span class="p">,</span> <span class="mi">150</span><span class="p">),</span> <span class="n">batch_size</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">class_mode</span><span class="o">=</span><span class="s">'binary'</span><span class="p">)</span>
<span class="n">history</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">fit_generator</span><span class="p">(</span><span class="n">train_generator</span><span class="p">,</span> <span class="n">steps_per_epoch</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">epochs</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">validation_data</span><span class="o">=</span><span class="n">validation_generator</span><span class="p">,</span> <span class="n">validation_steps</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="멀티-gpu-사용하기">멀티 GPU 사용하기</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">model</span> <span class="o">=</span> <span class="o">...</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">keras</span><span class="o">.</span><span class="n">utils</span><span class="o">.</span><span class="n">multi_gpu_model</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">gpus</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
<span class="n">model</span><span class="o">.</span><span class="nb">compile</span><span class="p">(</span><span class="n">loss</span><span class="o">=</span><span class="s">'binary_crossentropy'</span><span class="p">,</span> <span class="n">optimizer</span><span class="o">=</span><span class="n">keras</span><span class="o">.</span><span class="n">optimizers</span><span class="o">.</span><span class="n">RMSprop</span><span class="p">(</span><span class="n">lr</span><span class="o">=</span><span class="mf">1e-4</span><span class="p">),</span> <span class="n">metrics</span><span class="o">=</span><span class="p">[</span><span class="s">'acc'</span><span class="p">])</span>
<span class="o">...</span>
</code></pre></div></div>강우kangwoo@gmail.com쿠버네티스(kubernetes) 위에서 GPU 4개를 할당한 Jupyter를 사용하고 있다. Jupyter Notebook 에서 텐서플로-케라스를 사용하고 있는데, 노트북을 1개 더 생성해서 작업 할 경우 OOM 에러가 발생하였다. ResourceExhaustedError (see above for traceback): OOM when allocating tensor with shape[3,3,128,128] and type float on /job:localhost/replica:0/task:0/device:GPU:0 by allocator GPU_0_bfc [[node conv2d_3/kernel/Initializer/random_uniform/RandomUniform (defined at <ipython-input-2-355421ac90ac>:1) ]] Hint: If you want to see a list of allocated tensors when OOM happens, add report_tensor_allocations_upon_oom to RunOptions for current allocation info. 양쪽 노트북에서 모드 GPU:0을 사용해서 생긴 문제인거 같다. 첫번째 노트북에서 GPU:0의 메모리를 거의 풀로 사용해버린 상태에서, 두번째 노트북에서 GPU:0을 사용하려니 메모리가 부족한 상태가 발생한것이다.