[Openshift] 미터링 도구 Prometheus + AlertManager 알람 (webhook + telegram)

리눅스/OpenShift|2023. 4. 24. 10:10
반응형

Kubernetes 의 표준 모니터링 도구라 불리는 프로메테우스 (Prometheus) 는 리소스로부터 metirc 을 수집하고 모니터링 하는 기능을 제공합니다. 여기에 AlertManager 를 설치하면 이벤트 발생시 알림을 받을 수 있고, 웹 UI 대시보드인 Grafana 를 설치하면 시각화된 모니터링 페이지를 구성 할 수 있게 됩니다.

Prometheus 는 Kubernetes 에서 많이 사용하지만 Openshift 명령어인 OC (Openshift CLI) 도 쿠버네티스 기반으로 동작을 하므로 본 매뉴얼에서는 oc 명령 위주로 작업을 진행하도록 하겠습니다.

Kubernetes 사용자는 oc 명령 대신 kubectl 로 바꿔 사용하면 됩니다.

 

공식 페이지

https://prometheus.io

https://grafana.com/

 

 

1. kube-state-metrics 설치

 

kube-state-metrics 는 Kubernetes 클러스터 내 오브젝트에 대한 지표 (metrics) 정보를 수집하는데 사용되는 필수적인 도구 입니다.

이 도구는 Kubernetes API 서버에 쿼리를 보내서 클러스터의 리소스 상태 및 구성 정보를 수집하고, 이 정보를 Prometheus 와 같은 모니터링 시스템에서 사용할 수 있는 형식으로 출력해 줍니다.
kube-state-metrics 를 사용하면 클러스터 내부의 다양한 (Node, Pod, ReplicaSet, Deployment, StatefulSet, Service 등) 리소스 상태를 쉽게 모니터링할 수 있습니다.

kube-state-metrics 를 사용하기 위해 아래 파일을 모두 작성합니다.

https://github.com/kubernetes/kube-state-metrics/tree/main/examples/standard 에서 배포하는 내용으로 작성하였으며 테스트하며 몇군데 수정하였습니다.

 

kube-state-metrics 를 어떤 버전으로 사용해야 하는지는 https://github.com/kubernetes/kube-state-metrics 에서 참고해주세요.

# oc version

Client Version: 4.12.0
Kustomize Version: v4.5.7
Server Version: 4.12.0
Kubernetes Version: v1.25.4+77bec7a

 

1) ClusterRoleBinding 생성

# vi metrics-crb.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/name: kube-state-metrics
    app.kubernetes.io/version: 2.7.0
  name: kube-state-metrics
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kube-state-metrics
subjects:
- kind: ServiceAccount
  name: kube-state-metrics
  namespace: kube-system

 

2) ClusterRole 생성

# vi metrics-cr.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/name: kube-state-metrics
    app.kubernetes.io/version: 2.7.0
  name: kube-state-metrics
rules:
- apiGroups:
  - ""
  resources:
  - configmaps
  - secrets
  - nodes
  - pods
  - services
  - serviceaccounts
  - resourcequotas
  - replicationcontrollers
  - limitranges
  - persistentvolumeclaims
  - persistentvolumes
  - namespaces
  - endpoints
  verbs:
  - list
  - watch
- apiGroups:
  - apps
  resources:
  - statefulsets
  - daemonsets
  - deployments
  - replicasets
  verbs:
  - list
  - watch
- apiGroups:
  - batch
  resources:
  - cronjobs
  - jobs
  verbs:
  - list
  - watch
- apiGroups:
  - autoscaling
  resources:
  - horizontalpodautoscalers
  verbs:
  - list
  - watch
- apiGroups:
  - authentication.k8s.io
  resources:
  - tokenreviews
  verbs:
  - create
- apiGroups:
  - authorization.k8s.io
  resources:
  - subjectaccessreviews
  verbs:
  - create
- apiGroups:
  - policy
  resources:
  - poddisruptionbudgets
  verbs:
  - list
  - watch
- apiGroups:
  - certificates.k8s.io
  resources:
  - certificatesigningrequests
  verbs:
  - list
  - watch
- apiGroups:
  - discovery.k8s.io
  resources:
  - endpointslices
  verbs:
  - list
  - watch
- apiGroups:
  - storage.k8s.io
  resources:
  - storageclasses
  - volumeattachments
  verbs:
  - list
  - watch
- apiGroups:
  - admissionregistration.k8s.io
  resources:
  - mutatingwebhookconfigurations
  - validatingwebhookconfigurations
  verbs:
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - networkpolicies
  - ingressclasses
  - ingresses
  verbs:
  - list
  - watch
- apiGroups:
  - coordination.k8s.io
  resources:
  - leases
  verbs:
  - list
  - watch
- apiGroups:
  - rbac.authorization.k8s.io
  resources:
  - clusterrolebindings
  - clusterroles
  - rolebindings
  - roles
  verbs:
  - list
  - watch

 

3) ServiceAccount 생성

# vi metrics-sa.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/name: kube-state-metrics
    app.kubernetes.io/version: 2.7.0
  name: kube-state-metrics
  namespace: kube-system

 

4) Deployment 생성

# vi metrics-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: kube-state-metrics
    app.kubernetes.io/version: 2.7.0
  name: kube-state-metrics
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: kube-state-metrics
  template:
    metadata:
      labels:
        app.kubernetes.io/name: kube-state-metrics
        app.kubernetes.io/version: 2.7.0
    spec:
      automountServiceAccountToken: true
      containers:
      - image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.7.0
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          timeoutSeconds: 5
        name: kube-state-metrics
        ports:
        - containerPort: 8080
          name: http-metrics
        - containerPort: 8081
          name: telemetry
        readinessProbe:
          httpGet:
            path: /
            port: 8081
          initialDelaySeconds: 5
          timeoutSeconds: 5
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          runAsUser: 65534
          seccompProfile:
            type: RuntimeDefault
      nodeSelector:
        kubernetes.io/os: linux
      serviceAccountName: kube-state-metrics

 

5) Service 생성

외부에서 수집이 잘 되고 있는지 확인하기 위해 yaml 파일에서 NodePort 형식으로 31000 포트 설정을 추가 합니다.

# vi metrics-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: kube-state-metrics
    app.kubernetes.io/version: 2.7.0
  name: kube-state-metrics
  namespace: kube-system
spec:
  type: NodePort
  ports:
  - name: http-metrics
    port: 8080
    targetPort: http-metrics
    nodePort: 31000
  - name: telemetry
    port: 8081
    targetPort: telemetry
  selector:
    app.kubernetes.io/name: kube-state-metrics

 

생성한 5개의 yaml 파일을 적용합니다.

# oc apply -f metrics-crb.yaml
# oc apply -f metrics-cr.yaml

# oc apply -f metrics-sa.yaml
# oc apply -f metrics-deployment.yaml
# oc apply -f metrics-service.yaml

 

kube-state-metrics Service 상태를 확인합니다.

kube-state-metrics 의 ClusterIP 는 Prometheus 에서 접근하기 위해 사용하므로 메모하거나 기억해 둡니다.

# oc get service -n kube-system
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
kube-state-metrics   NodePort    172.30.224.58   <none>        8080:31000/TCP,8081:32325/TCP   4m30s
kubelet              ClusterIP   None            <none>        10250/TCP,10255/TCP,4194/TCP    12d

 

kube-state-metrics Pod 상태를 확인합니다.

# oc get pod -n kube-system -o wide
NAME                                  READY   STATUS    RESTARTS   AGE     IP            NODE                    NOMINATED NODE   READINESS GATES
kube-state-metrics-74cfc87b67-9nwpd   1/1     Running   0          4m38s   10.128.3.13   worker01.az1.sysdocu.kr   <none>           <none>

 

웹브라우저로 호스트명과 포트 31000 을 이용해 접속하면 metrics (수집 항목) 와 healthz (데몬 상태) 메뉴가 보입니다.

http://worker01.az1.sysdocu.kr:31000

- metrics (수집 항목) : 주석이 해제되어 있는 항목만 수집합니다.

- healthz (데몬 상태) : OK 라고 떠야 합니다.

 

 

2. Prometheus 설치

 

아래 예제파일에서 kube-state-metrics IP 를 제외하고 그대로 적용해도 동작하므로 그대로 복사하여 적용해 보도록 합니다.

다음과 같은 내용의 yaml 파일을 모두 만들어 놓습니다.

 

1) ClusterRole 생성

프로메테우스 컨테이너가 쿠버네티스 API 에 접근할 수 있도록 권한을 부여 합니다.

# vi prometheus-cr.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus
  namespace: monitoring
rules:
- apiGroups: [""]
  resources:
  - nodes
  - nodes/proxy
  - services
  - endpoints
  - pods
  verbs: ["get", "list", "watch"]
- apiGroups:
  - extensions
  resources:
  - ingresses
  verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
  verbs: ["get"]

 

* 설명

metadata: name : prometheus 라는 이름의 클러스터 역할을 생성하고 모니터링 권한을 정의 합니다.

metadata: namespace : 역할을 생성할 위치를 말합니다. 본 매뉴얼에서는 monitoring 이라는 프로젝트 (네임스페이스) 로 정했습니다.

rules: resources : ClusterRole 이 접근할 리소스를 지정합니다. (여기에서는 nodes, nodes/proxy, services, endpoints, pods 지정)
rules: verbs: ["get", "list", "watch"] : 리소스에 대한 허용된 동작을 지정합니다. (여기에서는 get, list, watch 동작 허용)
rules: apiGroups: resources: verbs: ["get", "list", "watch"] : configmaps 리소스에 대한 동작을 허용합니다. (여기에서는 get, list, watch 동작 허용)
rules: nonResourceURLs: verbs: ["get"] : /metrics URL 에 대한 get 동작을 허용합니다.

 

2) ClusterRoleBinding 생성

# vi prometheus-crb.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: prometheus
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: prometheus
subjects:
- kind: ServiceAccount
  name: default
  namespace: monitoring

 

* 설명

ClusterRoleBinding : monitoring 이름의 클러스터 역할 연결을 생성합니다.

                                   > monitoring 프로젝트 내의 prometheus 라는 클러스터 역할 연결 생성

                                   > monitoring 이라는 클러스터 역할 지정

 

3) ConfigMap 생성

ConfigMap 은 크게 두가지로 나뉘는데 이는 아래와 같습니다.

- prometheus.rules : 알람을 받기위한 조건을 설정합니다.

- prometheus.yml : 수집할 metric 종류와 주기를 설정합니다.

 

# vi prometheus-cm.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-server-conf
  labels:
    name: prometheus-server-conf
  namespace: monitoring
data:
  prometheus.rules: |-
    groups:

    # Pod CPU ====================
    - name: Pod CPU alert # 알림 그룹
      rules:
      - alert: Pod CPU usage rate over 90% # AlertManager 에 출력되는 알람 제목
        # 알림 트리거 조건
        expr: sum(rate(container_cpu_usage_seconds_total{pod!=""}[1m])) by (namespace, pod) > 0.9
        for: 1m # 지정된 시간 (1분) 동안 조건이 유지되어야 알람 트리거 발생
        labels:
          severity: warning # 종류로는 fatal, warning, error, critical, info, debug 외에 custom 입력가능
        # 주석. 생략 가능
        annotations:
          summary: Pod CPU usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}

      - alert: Pod CPU usage rate over 80%
        expr: 0.9 >= sum(rate(container_cpu_usage_seconds_total{pod!=""}[1m])) by (namespace, pod) > 0.8
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: Pod CPU usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}

      - alert: Pod CPU usage rate over 70%
        expr: 0.8 >= sum(rate(container_cpu_usage_seconds_total{pod!=""}[1m])) by (namespace, pod) > 0.7
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: Pod CPU usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}

      - alert: Pod CPU usage rate over 60%
        expr: 0.7 >= sum(rate(container_cpu_usage_seconds_total{pod!=""}[1m])) by (namespace, pod) > 0.6
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: Pod CPU usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}

      - alert: Pod CPU usage rate over 50%
        expr: 0.6 >= sum(rate(container_cpu_usage_seconds_total{pod!=""}[1m])) by (namespace, pod) > 0.5
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: Pod CPU usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}

      - alert: Pod CPU usage rate over 40%
        expr: 0.5 >= sum(rate(container_cpu_usage_seconds_total{pod!=""}[1m])) by (namespace, pod) > 0.4
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: Pod CPU usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}

      - alert: Pod CPU usage rate over 30%
        expr: 0.4 >= sum(rate(container_cpu_usage_seconds_total{pod!=""}[1m])) by (namespace, pod) > 0.3
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: Pod CPU usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}

      - alert: Pod CPU usage rate over 20%
        expr: 0.3 >= sum(rate(container_cpu_usage_seconds_total{pod!=""}[1m])) by (namespace, pod) > 0.2
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: Pod CPU usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}

      - alert: Pod CPU usage rate over 10%
        expr: 0.2 >= sum(rate(container_cpu_usage_seconds_total{pod!=""}[1m])) by (namespace, pod) > 0.1
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: Pod CPU usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}


    # 제대로 종료되지 않은 Pod 확인 ====================
    - name: Pod state
      rules:
      - alert: Pods blocked in terminating state
        expr: count(kube_pod_deletion_timestamp) by (namespace, pod) * count(kube_pod_status_reason{reason="NodeLost"} == 0) by (namespace, pod) > 0
        for: 5m
        labels:
          severity: notice
        annotations:
          summary: Pods blocked in terminating state
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} blocked in Terminating state.

    # Pod Memory ====================
    - name: Pod Memory alert
      rules:
      - alert: Pod Memory usage rate over 1G
        expr: sum(rate(container_memory_usage_bytes{pod!=""}[1m])) by (namespace, pod) > 1000000000 # 1G 이상
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: Pod Memory usage is high
          description: Pod {{ $labels.namespace }}/{{ $labels.pod }} value is {{ $value }}"

    # Node CPU ====================
    - name: Node CPU Alert
      rules:
      - alert: Node CPU usage rate over 90%
        expr: (100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)) > 90
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "High Node CPU Usage"
          description: "The CPU usage of the node {{ $labels.node }} is above 50%."

    # 시스템 다운 ====================
    - name: Instances down alert
      rules:
      - alert: Instance is Down
        expr: up == 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Instance {{ $labels.instance }} down" # 여기에서 labels.instance 는 115.68.142.103:6443 형태로 출력
          description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes." # 여기에서 labels.job 은 kubernetes-service-endpoints 형태로 출력


  # 프로메테우스 동작에 대한 configuration
  prometheus.yml: |-

    # 전반적인 설정
    global:
      scrape_interval: 5s     # scrape 하는 주기 (기본 1분)
      evaluation_interval: 5s # Rules 들을 평가하는 주기 (기본 1분)

    # 규칙을 로딩하고 evaluation_interval 설정에 따라 정기적으로 평가
    rule_files:
      - /etc/prometheus/prometheus.rules

    # Alert 보낼곳 (AlertManager) 설정
    alerting:
      alertmanagers:
      - scheme: http
        static_configs:
        - targets:
          - "alertmanager.monitoring.svc:9093"

    # metrics 정보를 가져오는 곳 (job_name 단위로 구분)
    scrape_configs:
      - job_name: 'kubernetes-apiservers'
        kubernetes_sd_configs:
        - role: endpoints
        scheme: https
        tls_config:
          ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
        relabel_configs:
        - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
          action: keep
          regex: default;kubernetes;https

      - job_name: 'kubernetes-nodes'
        scheme: https
        tls_config:
          ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
        kubernetes_sd_configs:
        - role: node
        relabel_configs:
        - action: labelmap
          regex: __meta_kubernetes_node_label_(.+)
        - target_label: __address__
          replacement: kubernetes.default.svc:443
        - source_labels: [__meta_kubernetes_node_name]
          regex: (.+)
          target_label: __metrics_path__
          replacement: /api/v1/nodes/${1}/proxy/metrics

      - job_name: 'kubernetes-pods'
        kubernetes_sd_configs:
        - role: pod
        relabel_configs:
        - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
          action: keep
          regex: true
        - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
          action: replace
          target_label: __metrics_path__
          regex: (.+)
        - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
          action: replace
          regex: ([^:]+)(?::\d+)?;(\d+)
          replacement: $1:$2
          target_label: __address__
        - action: labelmap
          regex: __meta_kubernetes_pod_label_(.+)
        - source_labels: [__meta_kubernetes_namespace]
          action: replace
          target_label: kubernetes_namespace
        - source_labels: [__meta_kubernetes_pod_name]
          action: replace
          target_label: kubernetes_pod_name

      - job_name: 'kube-state-metrics'
        static_configs:
          - targets: ['172.30.224.58:8080']

      - job_name: 'kubernetes-cadvisor'
        scheme: https
        tls_config:
          ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
        kubernetes_sd_configs:
        - role: node
        relabel_configs:
        - action: labelmap
          regex: __meta_kubernetes_node_label_(.+)
        - target_label: __address__
          replacement: kubernetes.default.svc:443
        - source_labels: [__meta_kubernetes_node_name]
          regex: (.+)
          target_label: __metrics_path__
          replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor

      - job_name: 'kubernetes-service-endpoints'
        kubernetes_sd_configs:
        - role: endpoints
        relabel_configs:
        - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
          action: keep
          regex: true
        - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
          action: replace
          target_label: __scheme__
          regex: (https?)
        - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
          action: replace
          target_label: __metrics_path__
          regex: (.+)
        - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
          action: replace
          target_label: __address__
          regex: ([^:]+)(?::\d+)?;(\d+)
          replacement: $1:$2
        - action: labelmap
          regex: __meta_kubernetes_service_label_(.+)
        - source_labels: [__meta_kubernetes_namespace]
          action: replace
          target_label: kubernetes_namespace
        - source_labels: [__meta_kubernetes_service_name]
          action: replace
          target_label: kubernetes_name


 

위 설정에서 클러스터 오브젝트 메트릭스를 수집하는 kube-state-metrics 의 targets 에 Cluster IP 를 설정하였습니다.

원래 도메인으로 설정해야 Pod 가 재시작 되면 바뀌는 IP 도 자연스럽게 연결이 될텐데, service 생성하는 부분에서 테스트가 잘 안되었으므로 추후 업데이트 하기로하고, 우선 위에서 메모 또는 기억해 두었던 kube-state-metrics 의 Cluster IP 와 8080 포트를 설정합니다.

      - job_name: 'kube-state-metrics'
        static_configs:
          - targets: ['172.30.224.58:8080']

그리고 {{ $labels.pod }} 와 같은 코드를 사용할때 빈값이 전송 될경우, expr 쿼리가 올바른지 살펴봐야 합니다. Prometheus 대시보드나 promql 명령어 (아래에서 설명) 를 이용해 PromQL 쿼리 결과값을 확인해보세요. Element 에 pod 값이 있어야 {{ $labels.pod }} 로 값을 넘겨줄 수 있습니다.

 

4) Deployment 생성

Pod 를 생성하기 위한 설정입니다.

# vi prometheus-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus-deployment
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus-server
  template:
    metadata:
      labels:
        app: prometheus-server
    spec:
      containers:
        - name: prometheus
          image: prom/prometheus:v2.44.0
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
          args:
            - "--config.file=/etc/prometheus/prometheus.yml"
            - "--storage.tsdb.path=/prometheus/"
          ports:
            - containerPort: 9090
          volumeMounts:
            - name: prometheus-config-volume
              mountPath: /etc/prometheus/
            - name: prometheus-storage-volume
              mountPath: /prometheus/
      volumes:
        - name: prometheus-config-volume
          configMap:
            defaultMode: 420
            name: prometheus-server-conf
        - name: prometheus-storage-volume
          emptyDir: {}

 

5) Service 생성

컨테이너 외부에서 프로메테우스로 접근하기 위한 네트워크 설정입니다.

# vi prometheus-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: prometheus-service
  namespace: monitoring
  annotations:
      prometheus.io/scrape: 'true'
      prometheus.io/port:   '9090'
spec:
  selector:
    app: prometheus-server
  type: NodePort
  ports:
    - port: 8080
      targetPort: 9090
      nodePort: 30003

 

6) Node-exporter (DaemonSet & Service) 생성

Node-exporter 는 노드 서버 자체의 다양한 메트릭 (CPU, 디스크, 메모리의 사용률, 네트워크 트래픽 등) 을 수집할 수 있습니다.

이러한 정보를 수집하여 운영 중인 노드의 성능과 리소스 사용량을 모니터링 할 수 있습니다.

스케쥴링되는 노드 (Pod 가 생성되는 노드) 마다 Pod 가 자동 설치됩니다.

아래와 같은 내용으로 DaemonSet, Service 두 개를 함께 작성합니다.

# vi prometheus-node-exporter.yaml

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
  labels:
    k8s-app: node-exporter
spec:
  selector:
    matchLabels:
      k8s-app: node-exporter
  template:
    metadata:
      labels:
        k8s-app: node-exporter
    spec:
      containers:
      - image: prom/node-exporter
        name: node-exporter
        ports:
        - containerPort: 9100
          protocol: TCP
          name: http
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
          runAsNonRoot: true
          seccompProfile:
            type: RuntimeDefault
---
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: node-exporter
  name: node-exporter
  namespace: monitoring
spec:
  ports:
  - name: http
    port: 9100
    nodePort: 31672
    protocol: TCP
  type: NodePort
  selector:
    k8s-app: node-exporter

 

이제 monitoring 네임스페이스를 만들고 작성해두었던 모든 yaml 파일을 적용합니다.

# oc create ns monitoring
# oc apply -f prometheus-cr.yaml

# oc apply -f prometheus-crb.yaml

# oc apply -f prometheus-cm.yaml
# oc apply -f prometheus-deployment.yaml

# oc apply -f prometheus-service.yaml
# oc apply -f prometheus-node-exporter.yaml

 

생성된 Pod 를 확인합니다. 서비스를 NodePort 형태로 생성했으므로 외부 접근을 위해 라우트 생성을 추가로 할 필요가 없습니다.

node-exporter Pod 을 보면 각 노드 (worker01, worker02) 에 하나씩 배포되었기 때문에 각 노드의 컨테이너 모니터링이 가능합니다.

prometheus-deployment 이름으로 된 Pod 의 호스트명과 포트 30003 으로 직접 접근이 가능합니다.

# oc get pod -n monitoring -o wide
NAME                                     READY   STATUS    RESTARTS   AGE   IP             NODE                    NOMINATED NODE   READINESS GATES
node-exporter-57djp                      1/1     Running   0     12m   10.131.0.184   worker02.az1.sysdocu.kr   <none>           <none>
node-exporter-w5s8d                      1/1     Running   0     12m   10.128.2.239   worker01.az1.sysdocu.kr   <none>           <none>
prometheus-deployment-859bc6d5c7-dm5nv   1/1     Running   0     24m   10.128.2.237   worker01.az1.sysdocu.kr   <none>           <none>

 

웹브라우저로 접속하면 여러가지 메뉴 이용이 가능합니다.

http://worker01.az1.sysdocu.kr:30003

 

프로메테우스 대시보드 > Status > Targets 에서도 kube-state-metrics 의 메트릭 정보가 정상적으로 수집되는 것을 확인할 수 있습니다.

kube-state-metrics (1/1 up)

 

참고)

대시보드 Alerts 메뉴에서는 임계치 설정 등 모니터링 설정 상태가 출력됩니다.

그래프 메뉴는 값이 생성되지 않으면 출력되지않지만 가장 많은 데이터를 볼 수 있는 항목은 아래와 같습니다.

Graph > 풀다운메뉴에서 container_memory_cache 선택 후 'Execute' 클릭 > 아래 'Graph' 탭을 누르면 그래프가 출력되는것이 보입니다.

 

7) 로그 보관일 변경

프로메테우스 대시보드 > Status > Runtime & Build Information 으로 들어가면 Storage retention 값이 15d (default) 로 되어 있습니다. 15일치 로그만 보관하겠다는 의미인데, 서비스로 제공하기에 적은 기간이므로 이부분을 10y (10년) 으로 변경 설정해 보겠습니다.

* 참고사항

- 프로메테우스에서 지원되는 최소 보존 시간은 2시간입니다.

- 설정한 보관 기간까지 데이터가 꽉 찬 상태에서는 오래된 데이터가 자동 삭제되지 않고, 더이상 데이터가 수집되지 않는 현상이 발생합니다.

- 그러다가 보관 기간을 늘리면 멈춘 상태에서 수집되지 않았던 데이터를 한번에 수집하게 됩니다.

 

현재 상태를 확인합니다.

# oc -n openshift-monitoring get sts prometheus-k8s -oyaml |grep retention
        - --storage.tsdb.retention.time=15d

 

yaml 파일을 작성하고 적용합니다.

기존 환경에서 retention time 부분만 추가했습니다.

# vi retention.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: cluster-monitoring-config
  namespace: openshift-monitoring
data:
  config.yaml: |
    prometheusK8s:
      retention: 10y            # 기간으로 제한
      retentionSize: 10GiB # 용량으로 제한

 

용량 설정이 필요한 경우 retention 아래 retentionSize : 10GiB 와 같이 추가 설정이 가능합니다.

작성한 yaml 파일을 적용합니다.

# oc apply -f retention.yaml 
configmap/cluster-monitoring-config created

 

변경사항을 확인합니다.

# oc -n openshift-monitoring get sts prometheus-k8s -oyaml |grep retention 
        - --storage.tsdb.retention.time=10y

 

이후에는 바뀐 설정값으로 동작을 하게 되는데, (2h 로 조정 후 테스트로 확인) 아까 살펴본 Prometheus 대시보드에서는 그대로 15d 로 출력됩니다. 이는 버그로 확인됩니다. (인터넷 상에서도 누군가도 버그 같다고 함)

 

 

3. Node-Exporter 설치

 

이번에는 클러스터 내부 메트릭 정보 수집이 아닌, 외부 특정 서버의 메트릭을 수집하는 방법입니다.

저는 OCP 서버 (bastion) 를 모니터링 하기 위해서 해당 부분 설정을 진행하였습니다.

모니터링 대상이 프로메테우스의 데이터 포맷을 지원하지 않는 경우 Exporter 라는 에이전트를 설치해야 하는데

node-exporter, mysql-exporter, nginx-exporter, redis-exporter 등 여러 종류가 있어 목적에 맞는 에이전트를 설치해야 합니다.

 

여기에서는 서버 자원 메트릭을 가져오기 위해 Node-Exporter 바이너리 파일을 배치하는 방식으로 설치할 예정이며

클러스터 내의 프로메테우스와 연동시켜 모니터링 해보도록 하겠습니다.

 

1) 설치

(OCP 서버에서)

다운로드 : https://prometheus.io/download/#node_exporter

 

다운로드 사이트에서 나온 최신버전 (현재기준 v1.5.0) 의 리눅스용 파일 URL 을 복사하고 서버에서 다운로드를 합니다.

# wget https://github.com/prometheus/node_exporter/releases/download/v1.5.0/node_exporter-1.5.0.linux-amd64.tar.gz

 

압축을 해제하고 실행파일을 시스템 폴더로 이동합니다.

# tar xvzf node_exporter-1.5.0.linux-amd64.tar.gz

# cd node_exporter-1.5.0.linux-amd64

# mv node_exporter /usr/local/bin/

 

부팅시 자동 실행되도록 서비스 데몬으로 등록합니다.

# cd /etc/systemd/system/
# vi node_exporter.service

[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter

[Install]
WantedBy=multi-user.target

 

node_exporter 라는 계정을 생성하여 구동 권한을 줍니다.

이렇게하면 특정 계정으로만 실행되므로 시스템 보안성을 향상 시킬 수 있습니다.
# useradd --no-create-home --shell /bin/false node_exporter
# chown node_exporter.node_exporter /usr/local/bin/node_exporter

 

systemd 를 재호출하고, node_exporter 데몬을 부팅시 자동 실행되도록 합니다.
그리고 현재 데몬을 시작합니다.

# systemctl daemon-reload
# systemctl enable node_exporter
# systemctl start node_exporter

 

서비스 상태와 포트를 확인합니다.

# systemctl status node_exporter |grep Active

   Active: active (running) since 화 2023-04-25 08:24:21 KST; 5min ago

# netstat -nltp |grep node_exporter
tcp6       0      0 :::9100                 :::*                    LISTEN      14500/node_exporter 

 

2) 프로메테우스와 연동

클러스터의 프로메테우스에서 서버 모니터링이 되도록 ConfigMap yaml 설정 파일을 수정합니다.

원래의 파일이 있던 위치로 돌아가 맨 하단에 job_name 을 추가해 줍니다.

# cd

# vi prometheus-cm.yaml


...
  prometheus.yml: |-
...
    scrape_configs:
...
      - job_name: 'server-info'
        static_configs:
        - targets: ['115.68.142.99:9100']

 

* 설명

scrape_configs 에 job_name 을 추가 해줍니다.

- Exporter 가 제공하는 Endpoint 가 /metrics 라면 metrics_path 의 설정을 생략 가능합니다.
- http 로 연결할 경우 스키마 설정을 생략 가능합니다.

- targets : 두개 이상의 서버를 등록하려면 targets 에 아래와 같은 형태로 추가하면 됩니다.

호스트명으로 입력 할 수 있습니다.

['192.168.10.2:9100', '192.168.10.3:9100', 'worker02.az1.sysdocu.kr:9100']

저는 OCP (bastion) 서버 한대에서만 node_exporter 를 설치했기 때문에 IP 한 개의 값만 입력하였습니다.

 

변경한 설정값을 적용하기 위해 yaml 파일을 다시 적용하고, 이미 생성된 pod 를 삭제하여 재생성되도록 합니다.

# oc apply -f prometheus-cm.yaml

 

Pod 재가동은 Pod 를 삭제하여 Deployment 의 replicas 에 의해 자동 생성 되도록 진행하는 것입니다.

# oc get pod -n monitoring
NAME                                     READY   STATUS    RESTARTS   AGE
node-exporter-57djp                      1/1     Running   0          17h
node-exporter-w5s8d                      1/1     Running   0          17h
prometheus-deployment-859bc6d5c7-dm5nv   1/1     Running   0          17h

 

# oc delete pod prometheus-deployment-859bc6d5c7-dm5nv -n monitoring
pod "prometheus-deployment-859bc6d5c7-dm5nv" deleted

 

# oc get pod -n monitoring
NAME                                     READY   STATUS    RESTARTS   AGE
node-exporter-57djp                      1/1     Running   0          17h
node-exporter-w5s8d                      1/1     Running   0          17h
prometheus-deployment-859bc6d5c7-lgrwj   1/1     Running   0          16s

 

Pod 를 재생성해서 worker node 가 바뀌었을 수 있으므로 재확인 합니다.

# oc get pod -n monitoring -o wide

NAME                                     READY   STATUS    RESTARTS   AGE   IP             NODE                    NOMINATED NODE   READINESS GATES
node-exporter-57djp                      1/1     Running   0     17h   10.131.0.184   worker02.az1.sysdocu.kr              
node-exporter-w5s8d                      1/1     Running   0     17h   10.128.2.239   worker01.az1.sysdocu.kr              
prometheus-deployment-859bc6d5c7-dm5nv   1/1     Running   0     11m   10.128.2.237   worker02.az1.sysdocu.kr    <none>        <none>

 

주소와 포트 번호를 이용해 웹브라우저로 접속합니다.

http://worker02.az1.sysdocu.kr:30003

프로메테우스 대시보드 > Status > Targets 에 접근하면 server-info 의 메트릭 정보가 정상적으로 수집되는 것을 확인할 수 있습니다.

server-info (1/1 up)

 

 

4. AlertManager 설치

 

AlertManager 는 Prometheus 에서 진행하는 알림 프로젝트 입니다.

본 매뉴얼에서는 모니터링 상세 설정을 하고 설정한 임계치 초과시 webhook 서버를 통해 telegram 알람을 받는 형식으로 진행하였지만 AlertManager 는 E-mail, Slack, PagerDuty, Pushover, Webhook 등 다양한 형태의 알림 방법을 지원하므로 목적에 맞는 설정 방법을 찾아서 설정하면 됩니다.

AlertManager 에서 webhook 서버를 통하지 않고 telegram 메세지를 바로 보내는 방법은 사전에 지정한 관리자에게만 발송하도록 되어 있어 Pod 사용자가 모두 다를 경우에는 AlertManager 에서 telegram 메세지 보내기에 적합하지 않은것 같습니다.

그래서 custom 작업이 가능한 별도의 webhook 서버를 만들어 사용했습니다.

 

1) 설치

AlertManager 서비스를 생성할때 ConfigMap 을 두개 작성하는데, 하나는 알람을 받을 대상, 하나는 알람 메세지 정보입니다.

구성을 위해 ConfigMap 두개와 Pod 생성을 위한 Deployment, 네트워크 연결을 위한 Service 를 작성합니다.

# vi alertmanager-cm.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: alertmanager-config
  namespace: monitoring
data:
  config.yml: |-
    global:
      resolve_timeout: 5m

    route:
      group_by: ['alertname'] # prometheus.yml 의 targets -labels의 label_name 을 의미
      group_wait: 10s            # inhibit 조건 발생에 대한 중복을 방지하기 위한 알림 발생 전 대기시간 (여기에서는 inhibit_rules 미사용)
      group_interval: 5m        # 알림 간 시간 간격 설정
      repeat_interval: 1h        # 알림 재발생
      receiver: webhook         # 알림 설정 대상

    receivers:
      - name: webhook
        webhook_configs:
          - url: 'http://alert.sysdocu.kr/telegram_send.php' # 알림을 웹훅 서버로 전송 (전송이 안될경우 도메인말고 IP 로 해볼것)
            send_resolved: false

    templates:
      - '/etc/alertmanager/config/*.tmpl'

 

* 설명

- route: receiver: 값과 receivers: name: 값은 같아야 합니다.

- webhook_configs 의 url 은 별도의 웹서버입니다. 저는 webhook 서버에서 AlertManager 가 보내주는 데이터를 받을 telegram_send.php 을 만들었습니다. 이 파일은 데이터를 용도에 맞게 처리하게 되며 (telegram 메세지 발송), 처리된 결과를 다시 AlertManager 로 응답할 필요는 없습니다.

- send_resolved 는 Alertmanager가 알림의 해결 상태 (resolved) 를 전송할지 여부를 나타내는 옵션입니다. false 의 경우 알람이 발생되었을때만 웹훅 서버로 전송하고, 해결된 알람에 대해서는 전송하지 않습니다.

- url: 도메인으로 동작이 안될 경우 oc logs 명령으로 로그를 확인해보세요. 저의 경우 status code 503 에러 등 연결이 안된 경우에 도메인을 IP 로 바꿔 넣으니 잘 동작하였습니다. Pod 나 클러스터 어디에서도 DNS 응답이 잘 되었지만, 실제로 해보면 모든 도메인이 OCP 서버를 바라보고 있었는데, 원인을 찾게되면 내용을 추가하겠습니다.

 

[참고]  웹훅 서버 만들기 ==========

용도에 따라 웹훅 서버라고 불리지만 그냥 웹서버라고 이해하면 됩니다.

nginx 또는 httpd 웹서버에 데이터를 처리할 php 정도만 설치 하면 됩니다.

DocumentRoot 웹소스 디렉토리에 아래 코드를 만들어 넣으면 됩니다.

(전체 POST 데이터를 알림하는 소스함. 추후 용도에 맞게 원하는 데이터만 추출하여 Telegram 보낼 수 있게 해보세요)

# vi telegram_send.php

<?php
// AlertManager 로부터 받은 데이터를 처리 (알람, 기록 등) 합니다.
// 응답을 반환하지 않아도 됩니다.

$postBody = file_get_contents('php://input'); // POST 데이터 전체 읽기

function telegram_send($chat_id, $msg) {
      $bot_token = "1234567890:ABHjBqhcBSUlY30NwIhiwyxwOEBHoHOsIGs"; // Telegram 봇 토큰
      $params = 'chat_id=' . $chat_id . '&text=' . urlencode($msg);
      $TELEGRAM = 'https://api.telegram.org/bot' . $bot_token . '/sendMessage';
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $TELEGRAM);
      curl_setopt($ch, CURLOPT_HEADER, false);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_TIMEOUT, 5);
      curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
      curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
      $return = curl_exec($ch);
      curl_close($ch);
      return $return;
}

// podName 을 확인하고 DB 에서 ChatID 를 가져와 메세지를 작성해야하는데
// 여기에서는 DB 연결 부분은 제외하고, 임의로 ChatID 와 받은 알람 전체 내용을 입력 했습니다.
telegram_send("10000000", $postBody);
?>

 

웹훅 서버가 잘 동작하는지 확인하기 위해 샘플 json파일을 만들어 curl 명령으로 POST 전송을 해볼 수 있습니다.

# echo '{"site": "CPU over 10%"}' > sample.json
# curl -i \
-H "Host: alert.sysdocu.kr:80" \
-H "User-Agent: Alertmanager/0.25.0" \
-H "Content-Type: application/json" \
--request POST \
--data @sample.json \
http://alert.sysdocu.kr:80/telegram_send.php

============================

 

계속해서 AlertManager 설치를 이어 나가겠습니다.

# vi alertmanager-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: alertmanager
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: alertmanager
  template:
    metadata:
      name: alertmanager
      labels:
        app: alertmanager
    spec:
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      containers:
      - name: alertmanager
        image: prom/alertmanager:latest
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
          runAsNonRoot: true
        args:
          - "--config.file=/etc/alertmanager/config.yml"
          - "--storage.path=/alertmanager"
        ports:
        - name: alertmanager
          containerPort: 9093
        volumeMounts:
        - name: config-volume
          mountPath: /etc/alertmanager
      volumes:
      - name: config-volume
        configMap:
          name: alertmanager-config

 

# vi alertmanager-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: alertmanager
  namespace: monitoring
  annotations:
      prometheus.io/scrape: 'true'
      prometheus.io/path:   /
      prometheus.io/port:   '8080'
spec:
  selector:
    app: alertmanager
  type: NodePort
  ports:
    - port: 9093
      targetPort: 9093
      nodePort: 30005

 

작성한 yaml 파일을 적용하여 AlertManager 를 가동합니다.

# oc apply -f alertmanager-cm.yaml
# oc apply -f alertmanager-deployment.yaml
# oc apply -f alertmanager-service.yaml

 

Pod 정보에 출력된 호스트명과 포트 30005 번으로 접근하면 AlertManager 웹페이지가 출력됩니다.

# oc get pod -n monitoring -o wide
NAME                                     READY   STATUS    RESTARTS   AGE     IP             NODE                    NOMINATED NODE   READINESS GATES
alertmanager-5c694d4f4d-x5gqz            1/1     Running   0          5m43s   10.131.0.253   worker02.az1.sysdocu.kr   <none>           <none>
node-exporter-57djp                      1/1     Running   0          25h     10.131.0.184   worker02.az1.sysdocu.kr   <none>           <none>
node-exporter-w5s8d                      1/1     Running   0          25h     10.128.2.239   worker01.az1.sysdocu.kr   <none>           <none>
prometheus-deployment-859bc6d5c7-pshwz   1/1     Running   0          7h32m   10.128.3.22    worker01.az1.sysdocu.kr   <none>           <none>

 

웹브라우저로 접속이 가능합니다.

http://worker02.az1.sysdocu.kr:30005

 

이제 서버에 부하를 발생시켜 알람 메세지가 telegram 으로 수신되고, 웹페이지 로그 출력도 잘 되는지 확인해 보도록 합니다.

 

2) 알람 발생

CPU 사용률 10%, Memory 사용률 50% 를 임계치로 정하고, 해당 리소스 사용량이 초과되면 webhook 서버를 통해 telegram 메세지를 발송하게 설정하였습니다.

Stress Pod 를 생성하고 부하를 일으켜 실제 telegram 메세지가 잘 발송되는지 확인합니다.

(Stress Pod 생성이 번거로울 경우 아래 '임의 로그 생성' 부분을 참고하세요)

 

Pod 가 생성되면 stress -c 1 명령이 실행되도록 합니다. (CPU 1 core 부하 100%)

이는 컨테이너 생성시부터 아래 작성할 Deployment 삭제시까지 부하를 일으키게 됩니다.

# cat Dockerfile

FROM docker.io/progrium/stress
CMD ["-c", "1"]

 

# docker build -t stress-app .

# docker tag stress-app default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/stress
# docker push default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/stress

 

stress 응용프로그램을 배포하기 위해 yaml 파일을 작성합니다.

# vi stress-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: stress
  labels:
    app: stress
  namespace: project412
spec:
  containers:
  - name: stress
    image: default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/stress
    ports:
      - containerPort: 80
    resources:
      limits:
        cpu: "1"
        memory: "1Gi"
    securityContext:

      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL
      readOnlyRootFilesystem: true
      runAsNonRoot: true
      runAsUser: 65534
      seccompProfile:
        type: RuntimeDefault

  imagePullSecrets:
  - name: sysdocu

 

Resources 에 제한 (CPU 1개, Memory 1G) 을 두어 작성하였습니다.

작성한 yaml 파일을 적용면 Deployment 와 Pod 가 생성됩니다.

# oc apply -f stress-pod.yaml

pod/stress created

 

Pod 상태가 Running 중으로 잘 구동 되어야 합니다.

# oc get pod -o wide

NAME                             READY   STATUS    RESTARTS   AGE    IP             NODE                    NOMINATED NODE   READINESS GATES
stress-668d86686d-4l7dm   1/1     Running   0          15s    10.131.1.84    worker02.az1.sysdocu.kr   <none>           <none>

 

Prometheus 대시보드에 가면 Alerts 페이지에서 아래와 같이 모니터링 알림 조건을 만족하는 노드가 1개 있다고 출력됩니다.

/etc/prometheus/prometheus.rules > Container CPU alert
Container CPU usage rate over 10% (1 active)

 

AlertManager 웹페이지 Alerts 메뉴에서도 Prometheus 정보를 받아와서 출력 해주는 것을 볼 수 있습니다.

alertname="Container CPU usage rate over 10%" 1 alerts

 

이와 동시에 Telegram 메세지가 잘 수신 되었습니다.

 

* 임의 로그 생성

Pod 자원을 사용하지 않고 임의로 AlertManager 에 로그 이벤트를 밀어 넣는 간단한 방법이 있습니다.

명령어 설치 방법은 아래와 같습니다.

# wget https://github.com/prometheus/alertmanager/releases/download/v0.16.1/alertmanager-0.16.1.linux-amd64.tar.gz

# tar xvzf alertmanager-0.16.1.linux-amd64.tar.gz

# cd alertmanager-0.16.1.linux-amd64

# mv amtool /usr/local/bin/

# amtool --version
amtool, version 0.16.1 (branch: HEAD, revision: 571caec278be1f0dbadfdf5effd0bbea16562cfc)
  build user:       root@3000aa3a06c5
  build date:       20190131-15:05:40
  go version:       go1.11.5

 

수동으로 탐지 메세지를 입력하는 명령입니다.

AlertManager Pod 의 호스트명과 포트 30005 번을 이용하고, yaml 파일에서 설정했던 alertname 과 severity 를 맞추어 줍니다.

명령 실행 후 바로 리스트에서 확인되지 않고 몇 초 기다려야 합니다.

# amtool --alertmanager.url=http://worker02.az1.sysdocu.kr:30005 alert add alertname="Container CPU usage rate over 10%" severity="warning"

 

알람 데이터가 잘 입력되었는지 확인합니다.

# amtool alert --alertmanager.url=http://worker02.az1.sysdocu.kr:30005
Alertname                                      Starts At                Summary  
Container CPU usage rate over 10%  2023-04-28 02:06:10 UTC

 

원래 Prometheus 에서 탐지된 정보를 AlertManager 로 보내주는 방식이나, 테스트를 위해 AlertManager 로 직접 알람 정보를 넣었습니다.

그래서 Prometheus 대시보드에서는 안보이고 AlertManager 대시보드에만 임계치 초과 정보가 확인 가능합니다.

물론 AlertManager 로 입력된 데이터 Telegram 으로 수신이 잘 되었습니다.

 

[참고] amtool 명령어 사용 방법 ==========

(1) 알람 리스트 출력 (간단)

AlertManager 의 호스트명과 포트번호 30005 를 이용하여 알람 리스트를 출력하면 위에서 입력했던 임의의 알람이 확인됩니다.

(amtool 명령어 설치는 위 '임의 로그 생성' 에서 확인하세요)
# amtool alert --alertmanager.url=http://worker02.az1.sysdocu.kr:30005
Alertname                                         Starts At                Summary                
Container CPU usage rate over 10%  2023-04-28 02:06:10 UTC

 

(2) 알람 리스트 출력 (json 형태로 출력)

# amtool alert --alertmanager.url=http://worker02.az1.sysdocu.kr:30005 -o json
[{"labels":{"alertname":"Container CPU usage rate over 10%","severity":"warning"},"annotations":{},"startsAt":"2023-04-28T02:06:10..445599906Z","endsAt":"2023-04-28T02:09:10.445599906Z","generatorURL":"","status":{"state":"active","silencedBy":null,"inhibitedBy":null},"receivers":["telegram"],"fingerprint":"bba57b61590c89ba"}]

 

(3) 특정 알람 silence 처리

여러가지 항목을 함께 사용해 조건에 맞는것만 silence 할 수 있지만 여기에서는 유일한 키인 fingerprint 값을 이용해 처리하겠습니다.

comment 는 이유를 적는 메모란인데 silence 처리하기 위해 반드시 필요합니다. 확인했다는 뜻으로 ok 라고 간단히 적겠습니다.

# amtool silence add --alertmanager.url=http://worker02.az1.sysdocu.kr:30005 fingerprint="bba57b61590c89ba" --comment=ok
70ce770c-2280-4ef3-87fb-5f99bd812eb9


(4) silence 리스트 출력
# amtool silence --alertmanager.url=http://worker02.az1.sysdocu.kr:30005
ID                                    Matchers                      Ends At                  Created By  Comment  
70ce770c-2280-4ef3-87fb-5f99bd812eb9  fingerprint=bba57b61590c89ba  2023-04-28 03:08:28 UTC  root        ok       

(5) silence 만료 처리 (삭제)

silence 의 ID 값으로 삭제를 진행합니다.
# amtool silence expire 70ce770c-2280-4ef3-87fb-5f99bd812eb9 --alertmanager.url=http://worker02.az1.sysdocu.kr:30005

==================================

 

 

5. PromQL 사용 방법

 

Prometheus 에서 kube-state-metrics 를 통해 수집하는 각각의 Pod 자원 사용량을 조회하는 방법을 알아보겠습니다.

수집된 데이터는 PromQL 이라고 하는 쿼리 언어를 사용하여 조회 할 수 있습니다.

아래 예제들을 참고하여 원하는 데이터를 알람 발송 설정해 보세요.

 

1) API 로 데이터 조회

Prometheus API 를 통해 JSON 형태의 데이터를 가져올 수 있습니다.

다음은 Namespace 별 Pod 개수를 가져오는 쿼리 입니다.

# curl -X GET " http://worker01.az1.sysdocu.kr:30003/api/v1/query?query=count(kube_namespace_created)"

 

2) PromQL 로 데이터 조회

또는 PromQL 이라는 명령어를 이용해 데이터를 가져올 수 있는데, 시각적으로 보기 좋게 테이블 형태로 출력 해줍니다.

쿼리를 사용하기 위해 PromQL 을 설치합니다.

https://github.com/nalbury/promql-cli 에서 최신버전의 PromQL 과 사용방법 확인이 가능합니다.

# wget https://github.com/nalbury/promql-cli/releases/download/v0.3.0/promql-v0.3.0-linux-amd64.tar.gz

# tar xvzf promql-v0.3.0-linux-amd64.tar.gz

# mv promql /usr/local/bin/

# promql --version
promql version v0.2.1  // 배포를 잘못했는지 이전 버전이 출력 됌

 

Prometheus 대시보드에서 PromQL 을 사용할 수 있지만 CLI 환경에서도 사용해 보기 위해 PromQL 을 설치했습니다.

간단하게 Namespace 의 개수를 조회하고 출력이 잘 되는지 확인합니다.

# promql --host "http://worker01.az1.sysdocu.kr:30003" 'count(kube_namespace_created)'
VALUE    TIMESTAMP
71       2023-05-26T09:13:39+09:00

 

현재시간으로 71개의 네임스페이스가 확인되었습니다.

Prometheus 대시보드의 쿼리 입력창 바로 아래에 사용가능한 쿼리 예시가 풀다운메뉴로 제공되고 있으며, 쿼리 사용을 위한 공식 Document URL  (https://prometheus.io/docs/prometheus/latest/querying/basics/) 도 있으므로 상세 설정이 필요하신 분은 참고하시기 바랍니다.

몇가지 Pod 관련 데이터를 더 조회해 보겠습니다.

 

형식) promql --host "http://worker01.az1.sysdocu.kr:30003" '<PromQL 쿼리문>'

형식에 아래 쿼리문을 대입하여 사용하면 됩니다.

- Pod 별 CPU 사용률 : sum(rate(container_cpu_usage_seconds_total{pod!="", container!="POD"}[5m])) by (pod)

- 특정 Pod 의 CPU 사용률 : sum(rate(container_cpu_usage_seconds_total{pod!="", container!="POD"}[5m])) by (pod)

- 특정 Pod 의 5분전 CPU 사용률 : sum(rate(container_cpu_usage_seconds_total{namespace="project412", pod="stress", container!="POD"}[5m] offset 5m)) by (pod)

- 특정 Namespace 및 Pod 의 한달간 CPU 사용률 : 

- 특정 Namespace 및 Pod 의 한달간 트래픽 사용률 : 

- Namespace 전체 개수 : count(kube_namespace_created)
- Pod 전체 개수 : count(count by(pod)(container_spec_memory_reservation_limit_bytes{pod!=""}))

- Namespace 별 Pod 개수 : count(kube_pod_info) by (namespace)

특정 네임스페이스 (monitoring) 내에서 조회

- Pod 들의 CPU 사용률 : sum(rate(container_cpu_usage_seconds_total{namespace="monitoring"}[5m])) by (pod)

- Pod 들의 Memory 사용량 : sum(container_memory_usage_bytes{namespace="monitoring"}) by (pod)

- Pod 들의 In-bound 트래픽 양 (byte) : sum(rate(container_network_receive_bytes_total{namespace="monitoring"}[5m])) by (pod)

- Pod 들의 Out-bound 트래픽 양 (byte) : sum(rate(container_network_transmit_bytes_total{namespace="monitoring"}[5m])) by (pod)

 

3) Promtool 로 조회

가장 필요로 했던 부분이 Pod 의 일일 평균 CPU 사용량을 한달치 가져오는 것인데 PromQL 이나 Prometheus API 에서 원하는 값을 가져오기가 어려웠습니다. 날짜로 정렬해주지 않고 최종 데이터 하나만 출력되기 때문이였는데요.

다양한 조건으로 쿼리를 사용할 수 있도록 공식 사이트에서 제공되는 Promtool 을 사용하면 쉽게 해결이 됩니다.

Promtool 공식 홈페이지에 가면 다운로드 파일과 사용 방법을 확인 할 수 있습니다.
저는 linux amd64 버전으로 다운로드 하고 설치하였습니다.

URL : https://prometheus.io/docs/prometheus/latest/command-line/promtool/

위 URL 에서 최신 버전의 파일을 확인하고 다운로드 합니다.

# wget https://github.com/prometheus/prometheus/releases/download/v2.44.0/prometheus-2.44.0.linux-amd64.tar.gz
# tar xvzf prometheus-2.44.0.linux-amd64.tar.gz
# mv prometheus-2.44.0.linux-amd64/promtool /usr/local/bin/
# promtool --version
promtool, version 2.44.0 (branch: HEAD, revision: 1ac5131f698ebc60f13fe2727f89b115a41f6558)
  build user:       root@739e8181c5db
  build date:       20230514-06:18:11
  go version:       go1.20.4
  platform:         linux/amd64
  tags:             netgo,builtinassets,stringlabels

아래와 같이 쿼리를 사용하여 한달 치 데이터를 출력하였습니다. (일일 평균 CPU 사용률 한달치 출력)

형식) promtool query range <Prometheus서버:포트> <쿼리> --start=<시작시간 unix timestamp> --end=<종료시간 unix timestamp> --step=<조회간격>

# promtool query range http://worker01.az1.sysdocu.kr:30003 "avg(rate(container_cpu_usage_seconds_total{namespace='project412', pod='stress', container!='POD'}[1d]))" --start=1682866800 --end=1685545199 --step=1d
{} =>
0.062463003550851465 @[1685199600]
0.9981953068392649 @[1685286000]
0.9981756660420877 @[1685372400]
0.667332233021956 @[1685458800]

 

* 설명

2023-05-01 00:00:00 부터 2023-05-31 23:59:59 까지의 하루 평균 CPU 사용률 데이터를 가져오도록 하였으며 조회 간격은 1일입니다. 조회대상 stress Pod 는 1core 짜리이고, 1core 를 100% 부하 주도록 되어있는데 (부하율 1), 3일전 생성한 Pod 여서 금일까지 총 4개의 데이터가 출력되었으며, 금일은 시간이 다 안지났기 때문에 0.66 으로 데이터가 어제보다 작게 나왔습니다. 잘 출력이 되는 것 같습니다.

 

추가로 일일 out-bound 트래픽 (byte) 양을 집계 해보겠습니다.

트래픽 양을 측정하기 위해 사용되는 메트릭은 다음과 같습니다.
- container_network_receive_bytes_total : 컨테이너가 수신한 네트워크 바이트 수를 측정하는 메트릭입니다.
- container_network_receive_packets_total : 컨테이너가 수신한 네트워크 패킷 수를 측정하는 메트릭입니다.
- container_network_transmit_bytes_total : 컨테이너가 전송한 네트워크 바이트 수를 측정하는 메트릭입니다.
- container_network_transmit_packets_total : 컨테이너가 전송한 네트워크 패킷 수를 측정하는 메트릭입니다.

 

# promtool query range http://worker01.az1.sysdocu.kr:30003 "sum(increase(container_network_transmit_bytes_total{namespace='project412', pod='stress'}[1d]))" --start=1682866800 --end=1685545199 --step=1d
{} =>
70.12534720867937 @[1685199600]
1610.44330061364 @[1685286000]
1680.5090135203238 @[1685372400]
1120.3633424784455 @[1685458800]

 

출력된 값의 단위는 byte 입니다. 부하만 주는 용도이며 외부와의 연결이 전혀 없으므로 굉장히 낮은 트래픽 사용량을 보이고 있습니다.

 

반응형

댓글()

Openshift 4.12.0 MetalLB 로드발란서 설치 및 서비스 IP 대역 할당하기

리눅스/OpenShift|2023. 4. 6. 09:34
반응형

[ 참조 문서 ]

- 설치 : https://docs.openshift.com/container-platform/4.12/networking/metallb/metallb-operator-install.html

- 아이피 추가 : https://docs.openshift.com/container-platform/4.12/networking/metallb/metallb-configure-address-pools.html

 

AWS, MS Azure 등의 인프라에서 구성하는것이 아닌 BareMetal 형태의 단독 시스템들로 구성을 할 경우 LoadBalancer 제공을 위해 아래와 같이 추가 설정을 진행 할 수 있습니다.

클러스터 관리자는 MetalLB Operator 를 추가하여 클러스터 내의 MetalLB 인스턴스의 라이프사이클을 관리할 수 있습니다.

그러나 MetalLB 와 IP failover 는 호환되지 않습니다.

클러스터에서 IP failover 를 구성한 경우, Operator를 설치하기 전에 IP failover를 제거하는 단계를 수행해야 합니다. (제거 과정은 생략합니다)

 

 

1. MetalLB 설정

 

다음 명령을 실행하여 MetalLB Operator 네임스페이스를 만듭니다.

# oc create ns metallb-system
namespace/metallb-system created

 

OperatorGroup yaml 파일을 작성합니다.

# vi og.yaml

apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: metallb-operator
  namespace: metallb-system

 

작성한 yaml 파일을 적용합니다.

# oc apply -f og.yaml
operatorgroup.operators.coreos.com/metallb-operator created

 

Operator 그룹이 네임스페이스에 설치되어 있는지 확인합니다.

# oc get operatorgroup -n metallb-system
NAME               AGE
metallb-operator   8s

 

사용자 지정 리소스 (CR) 를 사용하여 YAML 파일을 저장합니다.

# vi metallb-sub.yaml

apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: metallb-operator-sub
  namespace: metallb-system
spec:
  channel: stable
  name: metallb-operator
  source: redhat-operators
  sourceNamespace: openshift-marketplace

 

적용하여 Subscription 을 생성합니다.

# oc apply -f metallb-sub.yaml
subscription.operators.coreos.com/metallb-operator-sub created

 

네임스페이스 라벨을 붙입니다.

# oc label ns metallb-system "openshift.io/cluster-monitoring=true"
namespace/metallb-system labeled

 

설치 계획이 네임스페이스에 있는지 확인합니다.

# oc get installplan -n metallb-system
NAME            CSV                                    APPROVAL    APPROVED
install-56ph2   metallb-operator.4.12.0-202303231115   Automatic   true

 

아래 명령으로 상태를 확인하면 시간이 지나 Installing 에서 Succeeded 로 변경되는 것이 확인됩니다.

# oc get clusterserviceversion -n metallb-system
NAME                                   DISPLAY            VERSION               REPLACES   PHASE
metallb-operator.4.12.0-202303231115   MetalLB Operator   4.12.0-202303231115              Succeeded

 

 

2. 클러스터에서 MetalLB 시작하기

 

MetalLB 생성을 위한 yaml 파일을 작성합니다.

# vi MetalLB.yaml

apiVersion: metallb.io/v1beta1
kind: MetalLB
metadata:
  name: metallb
  namespace: metallb-system

 

yaml 파일을 적용하여 MetalLB 를 생성합니다.

# oc apply -f MetalLB.yaml
metallb.metallb.io/metallb created

 

컨트롤러 생성이 확인되었습니다.

# oc get deployment -n metallb-system controller
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
controller   1/1     1            1           45s

 

시간이 지나면서 speaker 준비 개수가 하나씩 오르는 것이 확인됩니다.

# oc get daemonset -n metallb-system speaker
NAME      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
speaker   5         5         5       5            5           kubernetes.io/os=linux   2m17s

 

 

3. Metal 을 위한 배포 사양

 

PriorityClass yaml 파일을 작성합니다.

여기에서는 high-priority 클래스를 사용합니다.

# vi PriorityClass.yaml

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000

 

작성한 yaml 파일을 적용합니다.
# oc apply -f PriorityClass.yaml 
priorityclass.scheduling.k8s.io/high-priority created

 

MetalLB yaml 파일을 작성합니다.

priorityClassName 에 위에서 적용한 metadata: name: 값을 입력합니다.

# vi MetalLBPodConfig.yaml

apiVersion: metallb.io/v1beta1
kind: MetalLB
metadata:
  name: metallb
  namespace: metallb-system
spec:
  logLevel: debug
  controllerConfig:
    priorityClassName: high-priority
    runtimeClassName: myclass
  speakerConfig:
    priorityClassName: high-priority
    runtimeClassName: myclass
  affinity:
      podAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
             app: metallb
          topologyKey: kubernetes.io/hostname

 

작성한 yaml 파일을 적용합니다.

# oc apply -f MetalLBPodConfig.yaml
metallb.metallb.io/metallb configured

 

네임스페이스의 Pod 에 할당한 우선 순위 클래스를 표시합니다.

# oc get pods -n project412 -o custom-columns=NAME:.metadata.name,PRIORITY:.spec.priorityClassName
NAME                    PRIORITY
mysql-bd544cdb8-qvm59   <none>

 

 

4. MetalLB 배치에서의 Pod CPU 제한 설정

 

필요에 따라 다음에 포드 CPU 제한을 할당할 수 있습니다.

controller 와 speaker Pod 를 설정함으로써 MetalLB yaml 파일에서 CPU 제한을 정의 합니다.

controller 또는 speaker Pod 는 노드의 리소스를 관리하는 데 도움이 됩니다.

이를 통해 노드의 모든 Pod 가 워크로드 관리와 클러스터 하우스키핑에 필요한 컴퓨팅 리소스를 확보할 수 있습니다.

 

MetalLB yaml 파일을 작성합니다.

# vi CPULimits.yaml

apiVersion: metallb.io/v1beta1
kind: MetalLB
metadata:
  name: metallb
  namespace: metallb-system
spec:
  logLevel: debug
  controllerConfig:
    resources:
      limits:
        cpu: "200m"
  speakerConfig:
    resources:
      limits:
        cpu: "300m"

 

작성한 yaml 파일을 적용합니다.
# oc apply -f CPULimits.yaml 
metallb.metallb.io/metallb configured

 

Pod 이름을 확인하고 적용 상태를 자세하게 확인합니다.

# oc describe pod mysql-bd544cdb8-qvm59

(출력 생략)

 

 

5. 컨테이너 런타임 클래스 설정

 

선택적으로 컨테이너 런타임 클래스를 다음에 할당할 수 있습니다.

Windows 작업 부하에 대해서는 pod에 Windows 런타임 클래스를 할당할 수 있습니다.

이렇게 하면 pod 내의 모든 컨테이너가 이 런타임 클래스를 사용하게 됩니다.

 

RuntimeClass yaml 파일을 작성합니다.

# vi myRuntimeClass.yaml

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: myclass
handler: myconfiguration

 

작성한 yaml 파일을 적용합니다.
# oc apply -f myRuntimeClass.yaml 
runtimeclass.node.k8s.io/myclass created

 

MetalLB yaml 파일을 작성합니다.

# vi MetalLBRuntime.yaml

apiVersion: metallb.io/v1beta1
kind: MetalLB
metadata:
  name: metallb
  namespace: metallb-system
spec:
  logLevel: debug
  controllerConfig:
    runtimeClassName: myclass
    annotations: 
      controller: demo
  speakerConfig:
    runtimeClassName: myclass
    annotations: 
      speaker: demo

 

작성한 yaml 파일을 적용합니다.
# oc apply -f MetalLBRuntime.yaml
metallb.metallb.io/metallb configured

 

Pod 의 컨테이너 런타임을 표시하려면 다음 명령을 실행합니다.

# oc get pod -o custom-columns=NAME:metadata.name,STATUS:.status.phase,RUNTIME_CLASS:.spec.runtimeClassName

 

 

6. MetalLB Address Pool 추가

 

클러스터 관리자는 IP 주소 풀 (address pool) 을 추가하거나 수정, 삭제 할 수 있습니다.

MetalLB Operator 는 주소 풀 커스텀 리소스를 사용하여 MetalLB가 서비스에 할당할 수 있는 IP 주소를 설정합니다.

예제에서는 metallb-system 네임스페이스를 사용한다고 가정합니다.

 

IPAddressPool yaml 파일을 작성합니다.

# vi ipaddresspool.yaml

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  namespace: metallb-system
  name: doc-example
  labels: 
    zone: az1
spec:
  addresses:
  - 115.68.142.121-115.68.142.126

 

작성한 yaml 파일은 MetalLB 의 IPAddressPool 리소스를 정의하는 예시입니다. 이 리소스는 MetalLB 에서 사용할 IP 주소 풀을 구성합니다. 각 IP 주소 풀은 addresses 필드에 지정된 IP 범위로 정의됩니다.

- metadata: labels: zone: 리소스에 할당할 라벨을 지정합니다.
- spec: addresses: IP 주소 풀을 지정하는 섹션입니다. 각 IP 주소 풀은 - 로 구분된 범위로 지정됩니다.

                              이 예시에서는 115.68.142.121 부터 115.68.142.126 까지의 IP 범위를 지정했습니다. (IP 6개를 로드발란서 IP 로 사용)

                              떨어져있는 IP 를 추가하고 싶을 경우 한 행을 더 추가하면 됩니다.
이렇게 정의된 IPAddressPool 리소스를 적용하면 MetalLB 에서 해당 IP 주소 풀을 사용하여 로드발란서 IP 주소를 할당할 수 있습니다.

 

작성한 yaml 파일을 적용합니다.

# oc apply -f ipaddresspool.yaml

ipaddresspool.metallb.io/doc-example created

 

추가된 Address Pool 을 확인합니다.

# oc describe -n metallb-system IPAddressPool doc-example

(출력 생략)

 

아래는 다른 적용 예시 입니다.

 

IPv4 와 CIDR 범위를 이용한 설정입니다.

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: doc-example-cidr
  namespace: metallb-system
spec:
  addresses:
  - 192.168.100.0/24
  - 192.168.200.0/24
  - 192.168.255.1-192.168.255.5

 

MetalLB는 풀에서 IP 주소를 자동으로 할당하지 않도록 autoAssign 필드를 false 로 설정 할 수 있습니다.

서비스를 추가할 때, 풀에서 특정 IP 주소를 요청하거나 어노테이션에서 풀 이름을 지정하여 풀에서 사용 가능한 임의의 IP 주소를 요청할 수 있습니다.

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: doc-example-reserved
  namespace: metallb-system
spec:
  addresses:
  - 10.0.100.0/28
  autoAssign: false

 

반응형

댓글()

Openshift yaml 파일로 Pod 생성시 보안 (권한) 에러 메세지

리눅스/OpenShift|2023. 4. 4. 14:56
반응형

[ 에러 ]

아래와 같은 yaml 파일을 작성하여 Pod 를 생성하려고 하였습니다.

# vi app.yaml

apiVersion: v1
kind: Pod
metadata:
  name: app3
spec:
  containers:
  - name: app33
    image: docker.io/php:7.4-apache
    ports:
    - containerPort: 80

 

Pod 생성 명령시 아래와 같은 에러가 출력되었습니다.

 

# oc apply -f app.yaml

Warning: would violate PodSecurity "restricted:v1.24": allowPrivilegeEscalation != false (container "app33" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "app33" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "app33" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "app33" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")

 

 

[ 해결 ]

에러 파일에 대한 조치로 아래와 같이 다시 작성하여 Pod 를 정상적으로 생성하였습니다.

apiVersion: v1
kind: Pod
metadata:
  name: app4
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app44
    image: docker.io/php:7.4-apache
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
      runAsNonRoot: true

 

* 설명
allowPrivilegeEscalation: 컨테이너가 프로세스의 권한 상승을 허용하지 않도록 설정합니다.
capabilities: 컨테이너가 사용할 수 있는 권한(capabilities)을 제한합니다. 여기에서는 모든 권한을 제한하도록 drop=["ALL"]로 설정합니다.
runAsNonRoot: 컨테이너가 root 권한으로 실행되지 않도록 설정합니다.
seccompProfile: 컨테이너가 사용하는 seccomp 프로필을 설정합니다. 여기에서는 type을 RuntimeDefault로 설정합니다.

# oc apply -f app.yaml

pod/app4 created

 

반응형

댓글()

Openshift Pod 상태 로그 보는 방법 두가지

리눅스/OpenShift|2023. 3. 27. 08:48
반응형

oc get pods 로 출력되는 Pod 에 Running 상태가 아닌 값이 있을경우 원인을 확인하기 위해 보통 아래 두가지 방법을 사용합니다.

 

 

에러 확인

# oc get pods
NAME                         READY   STATUS         RESTARTS   AGE
my-app11-6c7c9dcd7c-z6cww    0/1     ErrImagePull   0          12s

 

 

1. oc logs

# oc logs my-app11-6c7c9dcd7c-z6cww
Error from server (BadRequest): container "my-app11" in pod "my-app11-6c7c9dcd7c-z6cww" is waiting to start: trying and failing to pull image

 

이미지를 가져올 수 없다는 로그가 확인 되었는데, 이미지를 왜 가져올 수 없는지는 아래처럼 자세히 살펴볼 수 있습니다.

 

 

2. oc describe

# oc describe pod my-app11-6c7c9dcd7c-z6cww
Name:             my-app11-6c7c9dcd7c-z6cww
Namespace:        deploy
Priority:         0
Service Account:  default
Node:             worker01.az1.sysdocu.kr/115.68.142.104
Start Time:       Fri, 24 Mar 2023 10:30:18 +0900
Labels:           deployment=my-app11
                  pod-template-hash=6c7c9dcd7c
Annotations:      k8s.v1.cni.cncf.io/network-status:
                    [{
                        "name": "openshift-sdn",
                        "interface": "eth0",
                        "ips": [
                            "10.128.2.44"
                        ],
                        "default": true,
                        "dns": {}
                    }]
                  k8s.v1.cni.cncf.io/networks-status:
                    [{
                        "name": "openshift-sdn",
                        "interface": "eth0",
                        "ips": [
                            "10.128.2.44"
                        ],
                        "default": true,
                        "dns": {}
                    }]
                  openshift.io/generated-by: OpenShiftNewApp
                  openshift.io/scc: anyuid
Status:           Pending
IP:               10.128.2.44
IPs:
  IP:           10.128.2.44
Controlled By:  ReplicaSet/my-app11-6c7c9dcd7c
Containers:
  my-app11:
    Container ID:   
    Image:          my-app11:latest
    Image ID:       
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Waiting
      Reason:       ImagePullBackOff
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-q4sh6 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             False 
  ContainersReady   False 
  PodScheduled      True 
Volumes:
  kube-api-access-q4sh6:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
    ConfigMapName:           openshift-service-ca.crt
    ConfigMapOptional:       <nil>
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason          Age                From               Message
  ----     ------          ----               ----               -------
  Normal   Scheduled       16m                default-scheduler  Successfully assigned deploy/my-app11-6c7c9dcd7c-z6cww to worker01.az1.sysdocu.kr
  Normal   AddedInterface  16m                multus             Add eth0 [10.128.2.44/23] from openshift-sdn
  Normal   Pulling         14m (x4 over 16m)  kubelet            Pulling image "my-app11:latest"
  Warning  Failed          14m (x4 over 16m)  kubelet            Failed to pull image "my-app11:latest": rpc error: code = Unknown desc = reading manifest latest in docker.io/library/my-app11: errors:
denied: requested access to the resource is denied
unauthorized: authentication required
  Warning  Failed   14m (x4 over 16m)   kubelet  Error: ErrImagePull
  Warning  Failed   14m (x6 over 16m)   kubelet  Error: ImagePullBackOff
  Normal   BackOff  69s (x63 over 16m)  kubelet  Back-off pulling image "my-app11:latest"

 

접근 권한 문제로 확인되었습니다.

이경우 secret 을 이용해 사용자 계정 생성 및 권한 부여 등의 작업을 진행하면 됩니다.

이렇게 서비스에 문제가 생기게 될 경우 로그를 확인하는 두개 명령을 사용해 보시기 바랍니다.

 

반응형

댓글()

Openshift GitLab-CE 컨테이너 배포하기

리눅스/OpenShift|2023. 3. 24. 10:21
반응형

GitLab 은 웹 기반의 Git 저장소 관리 및 지속적인 통합, 배포를 제공하는 소프트웨어 개발 플랫폼입니다.

GitLab-CE (Community Edition) 컨테이너 서비스를 배포하기 위해 아래와 같은 절차를 진행합니다.


gitlab-ce 이미지를 다운로드 받습니다.
# docker pull gitlab/gitlab-ce
* docker.io 주소를 빼도 다운로드 됩니다.

다운로드 받은 이미지를 이용하여 어플리케이션을 배포합니다.
# oc new-app docker.io/gitlab/gitlab-ce

Pod 상태 확인을 하면 Running 과 Error 를 반복하다가 최종 CrashLoopBackOff 상태로 종료됩니다.
# oc get pod
NAME                         READY   STATUS             RESTARTS      AGE
gitlab-ce-549cbd9f47-5xcw5   0/1     CrashLoopBackOff   2 (13s ago)   84s

아래와 같은 로그가 확인됩니다.
# oc logs gitlab-ce-549cbd9f47-5xcw5
Thank you for using GitLab Docker Image!
Current version: gitlab-ce=15.10.0-ce.0

Configure GitLab for your system by editing /etc/gitlab/gitlab.rb file
And restart this container to reload settings.
To do it use docker exec:

  docker exec -it gitlab editor /etc/gitlab/gitlab.rb
  docker restart gitlab

For a comprehensive list of configuration options please see the Omnibus GitLab readme
https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md

If this container fails to start due to permission problems try to fix it by executing:

  docker exec -it gitlab update-permissions
  docker restart gitlab

Cleaning stale PIDs & sockets
cat: /var/opt/gitlab/gitlab-rails/VERSION: No such file or directory
Preparing services...
ln: failed to create symbolic link '/opt/gitlab/service/sshd': Permission denied

현재 프로젝트 (project412) 서비스어카운트에 anyuid 권한이 없기 대문입니다.
아래와 같이 권한을 부여 합니다. (add-scc-to-user)
# oc adm policy add-scc-to-user anyuid system:serviceaccount:project412:default
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid added: "default"
- project412 : 현재 프로젝트명 (oc project 로 확인 가능)

오류 발생되었던 deployment 와 service 를 삭제하고 Pod 를 재생성 합니다.
# oc delete deployment gitlab-ce
# oc delete service gitlab-ce

# oc new-app docker.io/gitlab/gitlab-ce


상태를 확인해보면 상태값이 올바르게 Running 으로 표시되었습니다.
# oc get pods
NAME                         READY   STATUS    RESTARTS   AGE
gitlab-ce-7cfcc46b75-ltgx4   1/1     Running   0          62s

사용하는 서비스 포트를 확인합니다.
# oc get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                 AGE
gitlab-ce        ClusterIP   172.30.199.183   <none>        22/TCP,80/TCP,443/TCP   2m4s

같은 포트 번호로 라우팅을 생성하여 외부에서 접근 가능하도록 합니다.
# oc expose service gitlab-ce --port 80
route.route.openshift.io/gitlab-ce exposed
# oc get route
NAME             HOST/PORT                                   PATH   SERVICES         PORT    TERMINATION   WILDCARD
gitlab-ce        gitlab-ce-deploy.apps.az1.sysdocu.kr               gitlab-ce        80                    None

출력된 호스트명과 포트를 이용하여 브라우저로 접속하면 서비스가 정상 확인됩니다.
(GitLab Community Edition 로그인 화면)
http://gitlab-ce-deploy.apps.az1.sysdocu.kr

Pod 생성을 위해 주었던 서비스어카운트의 CSS 권한을 제거합니다. (remove-scc-from-user)
# oc adm policy remove-scc-from-user anyuid system:serviceaccount:project412:default
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid removed: "default"
- project412 : 현재 프로젝트명 (oc project 로 확인 가능)

반응형

댓글()

[Openshift 에러] remote error: tls: internal error

리눅스/OpenShift|2023. 3. 23. 11:21
반응형

[상황]
Pod 에 접속시도 할때 해당 메세지가 출력되거나,

# oc rsh mysql-bd544cdb8-m7p6n
Error from server: error dialing backend: remote error: tls: internal error


Pod 의 로그를 보려해도 같은 메세지가 출력됩니다.
# oc logs mysql-bd544cdb8-m7p6n
Error from server: Get "https://115.68.142.105:10250/containerLogs/deploy/mysql-bd544cdb8-m7p6n/mysql": remote error: tls: internal error


[해결]
보류중인 CSR (Certificate Signing Requests) 을 확인하고 Pending 되어 있는것을 확인합니다.
# oc get csr

출력된 이름을 이용하여 보류중인 CSR 을 승인합니다.
# oc adm certificate approve csr-c4zbq
certificatesigningrequest.certificates.k8s.io/csr-c4zbq approved

참고로 보류 중인 모든 CSR 을 승인하려면 다음 명령을 수행합니다.
# oc get csr -o go-template='{{range .items}}{{if not .status}}{{.metadata.name}}{{"\n"}}{{end}}{{end}}' | xargs --no-run-if-empty oc adm certificate approve

 

 

[참고]

한개의 CSR 을 삭제하려면

# oc delete csr <CSR 이름>

 

모든 CSR 을 한번에 지우려면

# oc delete csr --all

 

반응형

댓글()

Openshift registry 내부 레지스트리에 로그인되지 않는 경우

리눅스/OpenShift|2023. 3. 20. 11:20
반응형

레지스트리 도메인 인증서를 Let's encrypt 에서 발급받고 로그인 시도 하였을때 아래와 같은 에러가 출력되었습니다.

이는 Let's encrypt 발행자가 시스템에서 인증되지 않은 경우에 발생한 문제입니다.

 

# podman login -u admin -p 12345678 default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000

Error: error authenticating creds for "default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000": error pinging docker registry default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000: Get https://default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/v2/: x509: certificate signed by unknown authority

 

이 경우 아래와 같이 인증 과정을 거치면 로그인이 가능해 집니다.

인증서를 다운로드 (추출) 합니다.

 

# openssl s_client -showcerts -connect default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000 < /dev/null 2> /dev/null | openssl x509 -outform PEM > myregistry.crt

 

Podman 이 인증서를 참조할 수 있도록 인증서를 복사합니다.

# mkdir -p /etc/docker/certs.d/default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/

# cp -arp myregistry.crt /etc/docker/certs.d/default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/ca.crt

 

로그인을 시도 합니다.

# podman login -u admin -p 12345678 default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000
Login Succeeded!

 

반응형

댓글()

oc patch configs.imageregistry.operator.openshift.io 명령으로 삭제한 이미지 레지스트리 복구하기

리눅스/OpenShift|2023. 3. 17. 16:21
반응형

다음과 같이 삭제하였다면 다시 복원이 가능합니다.

 

[ 삭제 ]

# oc patch configs.imageregistry.operator.openshift.io cluster -p '{"spec":{"managementState":"Removed"}}' --type='merge'
config.imageregistry.operator.openshift.io/cluster patched

 

Terminating 상태이므로 시간이 지나 삭제 됩니다.

# oc get pods -n openshift-image-registry
NAME                                              READY   STATUS        RESTARTS        AGE
cluster-image-registry-operator-77bbb4466-c4nwt   1/1     Running       2 (3h22m ago)   4h8m
image-registry-5f94b4bb5b-r89vg                   1/1     Terminating   0               19m
node-ca-2lkrw                                     1/1     Running       0               104m
node-ca-nxlsk                                     1/1     Running       0               3h27m
node-ca-pcbjq                                     1/1     Running       0               3h27m
node-ca-sxm58                                     1/1     Running       0               104m
node-ca-zdx88                                     1/1     Running       0               3h27m

 

[ 복원 ]

지워진 상태를 확인합니다.

# oc get configs.imageregistry.operator.openshift.io cluster -o jsonpath='{.spec.managementState}'
Removed

 

상태를 변경합니다.

# oc patch configs.imageregistry.operator.openshift.io cluster -p '{"spec":{"managementState":"Managed"}}' --type='merge'
config.imageregistry.operator.openshift.io/cluster patched

 

상태를 재확인합니다.

# oc get configs.imageregistry.operator.openshift.io cluster -o jsonpath='{.spec.managementState}'
Managed

 

상태 변경 명령 직후 Pod 를 확인하면 image-registry 가 Terminating 으로 보이는데, 시간이 조금 더 지나면 Running 상태로 돌아옵니다.

image-registry 가 되살아난 것이 확인되었습니다.

# oc get pod -n openshift-image-registry -l docker-registry=default

NAME                                              READY   STATUS    RESTARTS        AGE
image-registry-6dfb9c8f99-xbzbd                   0/1     Running   0               5s

 

단, PVC 구성은 변경되지 않았으므로, PVC 구성 변경이 필요한 경우 PVC 및 Image Registry 삭제를 하고 아래 매뉴얼을 참고하여 재구성 합니다.

https://sysdocu.tistory.com/1776

 

반응형

댓글()

Openshift 4.12.0 응용프로그램 배포 - 개발 언어 (PHP, Python, Go, NodeJS, Java, Ruby)

리눅스/OpenShift|2023. 2. 22. 08:50
반응형

본 문서는 공식 Documents 를 참고하여 작성하였습니다.

https://docs.openshift.com/container-platform/4.12/openshift_images/create-images.html

 

여기에서 잘 되지 않아 헤매었는데, 현재까지 시행착오를 겪고 이해한 바로는 이렇습니다.

웹서버와 데이터베이스 Pod 는 가동 후 데이터 입력 및 설정을 추가로 하면 되는데,

개발 언어들은 이와 같은 방식으로 Pod 가동이 안됩니다.

(CrashLoopBackOff, Completed 또는 Error 발생)

 

개발 언어들은 두가지 (이미지 + 어플리케이션 소스 코드) 가 올바로 준비되어야 Running 상태로 가동이 됩니다.

- 이미지 : 개발 언어 이미지

- 응용프로그램 소스 : 개발 언어를 이용하여 개발한 소스 코드

 

웹서버와 같이 oc new-app docker.io/php 명령으로 PHP 이미지를 사용하여 새로운 Pod 를 만들면
PHP 이미지에는 실행할 소스 코드가 포함되어 있지 않기 때문에 컨테이너가 Completed 상태로 종료됩니다.
그래서 PHP 응용프로그램을 실행하려면, 이미지에 개발한 소스 코드를 포함시켜야 합니다.

본 매뉴얼에서 진행한 테스트 절차는 아래와 같습니다.

- 개발 소스 준비 > build 하여 소스코드를 이미지에 삽입 > Registry 에 등록 > yaml 작성 > 응용프로그램 배포

 

* 사전 이해

- docker build 명령은 Dockerfile 을 사용하여 새로운 이미지를 빌드하고, 이 이미지를 로컬에 저장합니다.

  docker new-build 명령은 Dockerfile 을 사용하여 새로운 이미지를 빌드하지만, 이 이미지를 메모리에 저장합니다.

  로컬 머신의 디스크 공간을 덜 사용하기 때문에, 빌드 후 즉시 이미지를 삭제할 경우 유용합니다.

- oc new-build 명령은 새로운 Docker 이미지를 처음부터 빌드하는데 사용되며,

  oc start-build 명령은 이미 생성된 BuildConfig를 사용하여 새로운 빌드를 시작하는데 사용됩니다.

- Podman 은 Docker CLI 와 호환되는 CLI 와 API 를 제공하므로, 대부분의 Docker 명령어를 사용할 수 있습니다.

  하지만 Podman 으로 Docker 이미지를 빌드하거나 실행하려면 Docker 데몬이 설치되어 있어야 합니다.

  여기에서는 docker 로 설명 드리지만 docker, podman 패키지가 모두 설치된 상태에서는 podman 명령을 사용해도 무관합니다.

  # yum -y install docker

  # systemctl enable docker

  # systemctl start docker

 

* 프레임워크

개발 언어 컨테이너 만으로는 컨테이너 포트 활성화가 되지 않아 웹서버나 프레임워크를 이용하여 포트를 활성화하고 웹페이지를 확인합니다.

PHP 는 apache 웹서버가 내장된 이미지를 사용하면 포트를 활성화 하고 PHP 코드를 실행할 수 있습니다.

나머지 각 언어들은 다음과 같은 프레임워크를 사용하여 웹 어플리케이션을 개발할 수 있습니다. 그리고 내장된 웹서버를 활용할 수 있습니다.

- PHP : php-apache 이미지

- Ruby : Ruby on Rails, Sinatra
- Python : Django, Flask
- Go : Gin, Echo
- Node.js : Express, Koa
- Java : Spring, Struts

 

 

1. PHP 배포

 

Openshift에서 PHP 를 배포할 때, 포트를 열고 웹페이지를 출력하려면 PHP 어플리케이션을 웹서버와 함께 실행해야 합니다.

이를 위해서는 PHP 와 웹서버가 함께 포함된 이미지를 사용해야 하며, 단순히 PHP 이미지만 사용하는 것은 불가능합니다.
Apache 웹서버와 PHP 가 함께 포함된 이미지 중에서 가장 일반적으로 사용되는 것은 php-apache 이미지입니다.

이 이미지는 Apache 웹서버와 PHP 를 설치하고 구성하여 특정포트에서 애플리케이션을 실행할 수 있도록 합니다.

 

진행할 예제는 php:7.4-apache 기준으로 작성하였습니다.
우선 php:7.4-apache 이미지를 다운로드 합니다.

# docker pull docker.io/php:7.4-apache

 

테스트용으로 PHP 소스 파일을 만듭니다.

# mkdir ./source

# vi ./source/index.php

<?php
echo "Good job";
?>

 

Dockerfile 을 아래 내용으로 작성합니다.
# vi Dockerfile

FROM docker.io/php:7.4-apache

# Apache 설정
#RUN a2enmod rewrite

# 소스 복사
COPY ./source/ /var/www/html/

# PHP 확장기능 설치
#RUN docker-php-ext-install pdo_mysql

# 기본 포트 변경
RUN sed -i 's/Listen 80/Listen 8080/' /etc/apache2/ports.conf
RUN sed -i 's/Listen 443/Listen 8443/' /etc/apache2/ports.conf

# Apache 서버 시작
CMD ["apache2-foreground"]

 

기본포트 80, 443 등 로컬 haproxy 가 사용하는 포트는 사용하지 못하므로 8080, 8443 등으로 변경해서 사용해야 합니다.

변경하지 않고 이미지를 만들게 되면 해당 이미지로 생성한 Pod 에서 아래와 같은 에러 메세지를 볼 수 있습니다.

# oc logs <Pod 이름>
(13)Permission denied: AH00072: make_sock: could not bind to address [::]:80
(13)Permission denied: AH00072: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down
AH00015: Unable to open logs

 

현재 디렉토리에 있는 Dockerfile 을 사용하여 Docker 이미지를 빌드 합니다.

형식) docker build --tag <새 이미지 이름> <Dockerfile 위치>

# docker build --tag my-app5 .

 

새로운 이미지가 확인 되었습니다.

# docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
my-app5                              latest              b0aeb2e3c8af        42 seconds ago      453 MB

docker.io/php                        7.4-apache          20a3732f422b        4 months ago        453 MB

 

새로 만든 PHP 이미지로 응용프로그램을 생성하고 상태를 확인합니다.

형식) oc new-app --image=<이미지 이름> --name=<Pod 이름 정하고 싶은 경우>

# oc new-app --image=my-app5

# oc get pods
NAME                           READY   STATUS             RESTARTS   AGE
my-app5-64b9b569fb-jlw5h   0/1     ImagePullBackOff   0          8s

 

* 에러 발생 이유

Pod 상태가 ErrImagePull / 자동 재시도 (또는 ImagePullBackOff / 완전 실패) 일 경우는 이미지가 존재하지 않거나 액세스 권한이 없는 경우 발생할 수 있습니다. 이러한 문제를 해결하려면, 이미지가 올바르게 빌드되어 이미지 레지스트리에 올바르게 업로드되었는지 확인하고, 이미지 레지스트리에 액세스 할 수 있는 권한을 가지고 있는지 확인해야 합니다.

그렇지만 저의 경우는 아래와 같았으며, 다른 방법으로 해결하였습니다.

 

로그를 살펴봅니다.

에러1

# oc logs my-app5-64b9b569fb-jlw5h
Error from server: Get "https://115.68.142.105:10250/containerLogs/project412/my-app5-64b9b569fb-jlw5h/my-app5": remote error: tls: internal error

 

위 로그는 TLS 연결 오류입니다. 만약 Docker가 로컬 호스트에 있는 경우, 인증서와 관련된 문제는 발생하지 않을 수 있습니다.

하지만 OpenShift 클러스터는 기본적으로 Docker 데몬을 직접 실행하지 않으므로, 로컬 호스트의 Docker 데몬에 이미지가 존재하는 경우에도 OpenShift 클러스터와 Docker 레지스트리 간의 연결 문제가 발생할 수 있습니다.

이 경우, oc new-app 명령어에서 Docker 이미지를 로컬 호스트의 Docker 데몬에서 가져오는 대신, OpenShift 클러스터 내에서 Docker 이미지를 빌드 하도록 지시할 수 있습니다. Dockerfile을 작성하고 oc new-build 명령어를 사용하여 Docker 이미지를 빌드해야 합니다.

 

[첫번째 방법]
다음과 같이 oc new-build 명령어를 사용하여 Dockerfile 을 빌드하면 됩니다.

 

형식) oc new-build --name=<app 이름> --binary --strategy=docker

# oc new-build --name=my-app --binary --strategy=docker

형식) oc start-build <app 이름> --from-dir=<Dockerfile 위치>

# oc start-build my-app --from-dir=.

형식) oc new-app <app 이름>

# oc new-app my-app

 

oc new-build 명령어는 이전에 생성된 Docker 이미지를 빌드하기 위한 빈 이미지 스트림을 만듭니다.

oc start-build 명령어는 이 이미지 스트림을 사용하여 Docker 이미지를 빌드합니다.

oc new-app 명령어는 이미지 배포 명령입니다.

 

[두번째 방법]

또는 아래와 같이 단순화 할 수 있습니다.

# oc import-image my-php:7.4-apache --from=docker.io/php:7.4-apache --confirm

 

이 명령어는 Docker Hub의 docker.io/php:7.4-apache 이미지를 가져와서 OpenShift 클러스터의 my-php:7.4-apache 이미지스트림으로 변환합니다. --confirm 옵션을 사용하면, 이미지스트림이 이미 존재할 경우 덮어쓰기를 확인하는 메시지가 표시됩니다.
이미지스트림으로 변환한 후에는, oc new-app 명령어에서 이미지스트림을 사용하여 애플리케이션을 배포할 수 있습니다.

 

형식) oc new-app <이미지스트림 이름>~<소스 위치>

# oc new-app my-php:7.4-apache~./source/

 

이 명령어는 my-php:7.4-apache 이미지스트림을 사용하여 새로운 애플리케이션을 생성하고, <path-to-source-code> 경로에 있는 소스 코드와 결합하여 배포합니다. (하지만 Dockerfile 추가 옵션 사용 불가)

 

에러2

# oc logs my-app11-6c7c9dcd7c-z6cww
Error from server (BadRequest): container "my-app11" in pod "my-app11-6c7c9dcd7c-z6cww" is waiting to start: trying and failing to pull image

 

위에러를 자세히 보기 위해 oc describe 명령을 사용합니다.

# oc describe pod my-app11-6c7c9dcd7c-z6cww
...

Events:
  Type     Reason          Age                From               Message
  ----     ------          ----               ----               -------
  Normal   Scheduled       16m                default-scheduler  Successfully assigned deploy/my-app11-6c7c9dcd7c-z6cww to worker01.az1.sysdocu.kr
  Normal   AddedInterface  16m                multus             Add eth0 [10.128.2.44/23] from openshift-sdn
  Normal   Pulling         14m (x4 over 16m)  kubelet            Pulling image "my-app11:latest"
  Warning  Failed          14m (x4 over 16m)  kubelet            Failed to pull image "my-app5:latest": rpc error: code = Unknown desc = reading manifest latest in docker.io/library/my-app5: errors: denied: requested access to the resource is denied
unauthorized: authentication required
  Warning  Failed   14m (x4 over 16m)   kubelet  Error: ErrImagePull
  Warning  Failed   14m (x6 over 16m)   kubelet  Error: ImagePullBackOff
  Normal   BackOff  69s (x63 over 16m)  kubelet  Back-off pulling image "my-app5:latest"

 

권한이 없어 인증이 필요하다는 것을 확인하였습니다.

이 문제는 아래와 같이 secret 을 생성해서 프로젝트에 권한을 부여하면 됩니다.

# oc create secret docker-registry docker-registry-login \
--docker-server=115.68.142.99:5000 \
--docker-username=sysdocu \
--docker-password=12345678 \
--namespace=project412
secret/docker-registry-login created

- 115.68.142.99 : 본 서버 (OCP 서버 IP)

 

# oc get secrets
NAME                       TYPE                                  DATA   AGE
builder-dockercfg-4jj5l    kubernetes.io/dockercfg               1      24h
builder-token-tpbk6        kubernetes.io/service-account-token   4      24h
default-dockercfg-hsnzx    kubernetes.io/dockercfg               1      24h
default-token-fps64        kubernetes.io/service-account-token   4      24h
deployer-dockercfg-l24hl   kubernetes.io/dockercfg               1      24h
deployer-token-wfrt8       kubernetes.io/service-account-token   4      24h
docker-registry-login      kubernetes.io/dockerconfigjson        1      35s

secret 생성을 확인하였습니다.

이제 해당 secret 정보를 deployment, service 에 적용하고 재생성 명령을 통해 Pod 를 가동해 보겠습니다.

기존 에러 발생한 deployment, service 설정 상태를 yaml 파일로 추출합니다.

# oc get deployment my-app5 -o yaml > deployment_my-app5.yaml
# oc get service my-app5 -o yaml > service_my-app5.yaml

 

파일을 열어 아래 옵션에 secret 정보를 입력합니다.

아래 위치에 옵션이 없으므로 옵션명과 secret 이름을 추가해 줍니다.

# vi deployment_my-app5.yaml

...
spec:
  template:
    spec:
      imagePullSecrets:
        - name: docker-registry-login
...

 

# vi service_my-app5.yaml

...

...

적용

# oc apply -f deployment_my-app11.yaml

 

Deployment 와 Service 를 모두 수정한 후, oc rollout 명령어를 사용하여 Pod를 재생성합니다.

# oc rollout latest deployment/my-app5

 

Openshift 에서 응용프로그램에 대한 라우트를 생성합니다.
# oc expose service my-app5 --port=8080

# oc get route
NAME          HOST/PORT                                     PATH   SERVICES      PORT       TERMINATION   WILDCARD
my-app5   my-app5-project412.apps.az1.sysdocu.kr          php-apache   8080-tcp                 None

 

이제 웹 브라우저를 열고 route 정보에 출력된 호스트네임과 포트로 접근하면 개발한 소스 코드가 출력됩니다.

# curl my-app5-project412.apps.az1.sysdocu.kr:8080

Good job

 

 

2. Python 배포

 

최신 버전의 python 이미지를 다운로드 합니다.

# docker pull docker.io/python:latest

 

빌드할때 필요한 파일을 미리 준비해 둡니다.

# mkdir ./source

# vi ./source/server.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello, World!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

 

모듈리스트를 작성한 파일을 생성합니다.

여러개의 필요한 모듈을 올릴 수 있지만 본 예제에서는 Flask 모듈만 올렸는데, Flask 프레임워크를 설치하여 웹서버 용도로 사용하기 위해서 입니다.

# vi source/requirements.txt

Flask

 

빌드하기 위해 Dockerfile 을 작성합니다.

# vi Dockerfile

FROM docker.io/python:latest
COPY ./source/ /app/
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 8080
CMD ["python", "server.py"]

 

* 설명

FROM python:latest : Python 의 최신 버전을 기반으로 하는 Docker 이미지를 사용합니다.
COPY ./source/ /app/ : 호스트의 ./source/ 디렉토리에 있는 파일들을 컨테이너 내부의 /app/ 디렉토리로 복사합니다.
WORKDIR /app : 이후 명령어들이 실행될 디렉토리를 /app 으로 설정합니다.

RUN pip install -r requirements.txt : 파일에 작성되어있는 모든 모듈을 설치합니다.

EXPOSE 8080 : 호스트의 8080 포트와 컨테이너 내부의 8080 포트를 연결합니다.
CMD ["python", "server.py"] : 컨테이너가 시작될 때 /app 디렉토리에서 server.py 를 찾아 python 명령으로 실행합니다.

 

빌드 실행하면 새로운 도커 이미지가 생성됩니다.

# docker build -t python-app .

# docker images |grep python-app
python-app                          latest            fc6586828737        5 seconds ago       921 MB

 

로컬의 도커 이미지를 Registry 에 올려놓고 사용하도록 합니다.

업로드 권한이 필요하므로 생성했던 secret 계정을 이용해 Registry 에 로그인을 합니다.

# docker login -u sysdocu -p 12345678 default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000
Login Succeeded

 

태그 설정을 하고 이미지를 업로드 합니다.

# docker tag python-app default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/python
# docker push default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/python

 

python 응용프로그램을 배포하기 위해 yaml 파일을 작성합니다.

# vi app.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python-sample
  template:
    metadata:
      labels:
        app: python-sample
    spec:
      containers:
      - name: python-sample
        image: default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/python
        ports:
        - containerPort: 8080
          protocol: TCP
      imagePullSecrets:
      - name: sysdocu
---
apiVersion: v1
kind: Service
metadata:
  name: python-sample
spec:
  type: LoadBalancer
  selector:
    app: python-sample
  ports:
    - name: python-sample
      protocol: TCP
      port: 8080
      targetPort: 8080

 

위 yaml 파일을 적용면 deployment, pod, service, endpoint 가 모두 생성됩니다.

# oc apply -f app.yaml

deployment.apps/python-sample created
service/python-sample created

 

추가로 외부에서 접근이 가능하도록 route 를 생성합니다.

# oc expose service python-sample --name=python-sample --port=8080

route.route.openshift.io/python-sample exposed

 

생성된 route 정보를 확인합니다.

# oc get route
NAME            HOST/PORT                                    PATH   SERVICES        PORT   TERMINATION   WILDCARD
python-sample   python-sample-project412.apps.az1.sysdocu.kr          python-sample   8080                 None

 

외부에서 호스트명을 이용해 컨테이너 접근이 가능합니다.

# curl python-sample-project412.apps.az1.sysdocu.kr
Hello, World!

 

 

3. Go (Golang) 배포

 

최신 버전의 go 이미지를 다운로드 합니다.

# docker pull docker.io/golang:latest

 

빌드할때 필요한 파일을 미리 준비해 둡니다.

# mkdir ./source

# vi ./source/gogin.go

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello, world!")
    })
    r.Run() // default port is 8080
}

 

빌드하기 위해 Dockerfile 을 작성합니다.

# vi Dockerfile

FROM golang:latest
ENV CGO_ENABLED 0
COPY ./source/ /app/
WORKDIR /app
RUN go mod init github.com/example/example
RUN go mod tidy
RUN go build -o gogin .
EXPOSE 8080
CMD ["/app/gogin"]

 

* 설명

FROM python:latest : Golang 의 최신 버전을 기반으로 하는 Docker 이미지를 사용합니다.

ENV CGO_ENABLED 0 : 환경 변수를 설정합니다. CGO_ENABLED 를 0 으로 설정하여 C 기반 라이브러리를 사용하지 않도록 합니다.

COPY ./source/ /app/ : 호스트의 ./source/ 디렉토리에 있는 파일들을 컨테이너 내부의 /app/ 디렉토리로 복사합니다.
WORKDIR /app : 이후 명령어들이 실행될 디렉토리를 /app 으로 설정합니다.

RUN go mod init github.com/example/example : 모듈을 초기화 할때 사용하는 명령이지만 잘못된 URL 을 입력해도 넘어갑니다.

                                                                             이 행이 있어야 다음 행으로 넘어갈 수 있어서 아무렇게나 입력하였습니다.

RUN go mod tidy : Go 모듈 시스템을 사용하여 의존성을 관리하고 go.mod 및 go.sum 파일을 업데이트합니다.

RUN go build -o gogin . : /app 디렉토리에 위치한 소스코드를 컴파일하여 gogin 바이너리 파일을 생성하는 명령입니다.

EXPOSE 8080 : 컨테이너가 사용할 포트 번호를 설정합니다. 이 경우, 8080번 포트를 사용합니다.
CMD ["/app/gogin"] : 컨테이너가 시작될 때 실행할 명령을 설정합니다. 이 경우, gogin 실행 파일을 실행하여 Go 애플리케이션을 실행합니다.

 

빌드 실행하면 새로운 도커 이미지가 생성됩니다.

# docker build -t go-app .

# docker images |grep go-app
go-app                          latest            f5e7faecac07        3 minutes ago       1.05 GB

 

로컬의 도커 이미지를 Registry 에 올려놓고 사용하도록 합니다.

# docker tag go-app default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/go
# docker push default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/go

 

Go 응용프로그램을 배포하기 위해 yaml 파일을 작성합니다.

# vi app.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: go-sample
  template:
    metadata:
      labels:
        app: go-sample
    spec:
      containers:
      - name: go-sample
        image: default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/go
        ports:
        - containerPort: 8080
          protocol: TCP
      imagePullSecrets:
      - name: sysdocu
---
apiVersion: v1
kind: Service
metadata:
  name: go-sample
spec:
  type: LoadBalancer
  selector:
    app: go-sample
  ports:
    - name: go-sample
      protocol: TCP
      port: 8080
      targetPort: 8080

 

위 yaml 파일을 적용면 deployment, pod, service, endpoint 가 모두 생성됩니다.

# oc apply -f app.yaml

deployment.apps/go-sample created
service/go-sample created

 

추가로 외부에서 접근이 가능하도록 route 를 생성합니다.

# oc expose service go-sample --name=go-sample --port=8080

route.route.openshift.io/go-sample exposed

 

생성된 route 정보를 확인합니다.

# oc get route
NAME            HOST/PORT                                    PATH   SERVICES        PORT   TERMINATION   WILDCARD
go-sample       go-sample-project412.apps.az1.sysdocu.kr              go-sample       8888                 None
python-sample   python-sample-project412.apps.az1.sysdocu.kr          python-sample   8080                 None

 

외부에서 호스트명을 이용해 컨테이너 접근이 가능합니다.

# curl go-sample-project412.apps.az1.sysdocu.kr
Hello, world!

 

 

4. NodeJS 배포

 

최신 버전의 nodejs 이미지를 다운로드 합니다.

# docker pull docker.io/node:latest

 

빌드할때 필요한 파일을 미리 준비해 둡니다.

# mkdir ./source

# vi ./source/index.js

var express = require('express')
var app = express()

app.get('/', function (req, res) {
    res.send('Hello World!')
})

app.listen(8080, function () {
    console.log('app listening on port 8080!')
})

 

# vi ./source/package.json

{
  "name": "node-sample",
  "version": "1.0.0",
  "description": "A simple Node.js application",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.17.2"
  }
}

 

빌드하기 위해 Dockerfile 을 작성합니다.

# vi Dockerfile

FROM node:latest
COPY ./source/ /app/
WORKDIR /app
RUN npm init
RUN npm install express
EXPOSE 8080
CMD ["npm", "start"]

 

* 설명

FROM python:latest : Golang 의 최신 버전을 기반으로 하는 Docker 이미지를 사용합니다.

COPY ./source/ /app/ : 호스트의 ./source/ 디렉토리에 있는 파일들을 컨테이너 내부의 /app/ 디렉토리로 복사합니다.
WORKDIR /app : 이후 명령어들이 실행될 디렉토리를 /app 으로 설정합니다.

RUN npm init -y : Node.js 프로젝트를 위한 package.json 파일을 생성합니다. 상호 작용모드로 진입하지 않도록 -y 옵션을 추가합니다.
RUN npm install express : Node.js 프로젝트에서 express 모듈을 설치합니다.

EXPOSE 8080 : 컨테이너가 사용할 포트 번호를 설정합니다.
CMD ["npm", "start"] : 컨테이너가 시작될 때 실행할 명령을 설정합니다. 여기서는 npm start를 실행합니다.

                                   이는 Node.js 프로젝트 내부의 package.json 파일에서 start 스크립트를 실행합니다.

 

빌드 실행하면 새로운 도커 이미지가 생성됩니다.

# docker build -t node-app .

# docker images |grep node-app
node-app                          latest            f5e7faecac07        3 minutes ago       1.05 GB

 

로컬의 도커 이미지를 Registry 에 올려놓고 사용하도록 합니다.

# docker tag node-app default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/node
# docker push default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/node

 

nodejs 응용프로그램을 배포하기 위해 yaml 파일을 작성합니다.

# vi app.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: node-sample
  template:
    metadata:
      labels:
        app: node-sample
    spec:
      containers:
      - name: node-sample
        image: default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/node
        ports:
        - containerPort: 8080
          protocol: TCP
      imagePullSecrets:
      - name: sysdocu
---
apiVersion: v1
kind: Service
metadata:
  name: node-sample
spec:
  type: LoadBalancer
  selector:
    app: node-sample
  ports:
    - name: node-sample
      protocol: TCP
      port: 8080
      targetPort: 8080

 

위 yaml 파일을 적용면 deployment, pod, service, endpoint 가 모두 생성됩니다.

# oc apply -f app.yaml

deployment.apps/node-sample created
service/node-sample created

 

추가로 외부에서 접근이 가능하도록 route 를 생성합니다.

# oc expose service node-sample --name=node-sample --port=8080

route.route.openshift.io/node-sample exposed

 

생성된 route 정보를 확인합니다.

# oc get route
NAME            HOST/PORT                                    PATH   SERVICES        PORT   TERMINATION   WILDCARD
go-sample       go-sample-project412.apps.az1.sysdocu.kr              go-sample       8888                 None
node-sample     node-sample-project412.apps.az1.sysdocu.kr          node-sample   8080                 None
python-sample   python-sample-project412.apps.az1.sysdocu.kr          python-sample   8080                 None

 

외부에서 호스트명을 이용해 컨테이너 접근이 가능합니다.

# curl node-sample-project412.apps.az1.sysdocu.kr
Hello World!

 

 

5. Java 배포

 

특정 버전의 alpine (경량) java 이미지를 다운로드 합니다.

# docker pull docker.io/openjdk:17-alpine

 

빌드할때 필요한 파일을 미리 준비해 둡니다.

아래 java 소스를 보면 웹서버를 담당하는 것은 JDK 에서 제공하는 HTTP 서버 API 입니다.

이 API 를 사용하면 간단한 웹 서버를 만들 수 있지만, Spring 과 같은 프레임워크에 비해 제공하는 기능이 제한적입니다.

# mkdir ./source

# vi ./source/HelloWorld.java

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class HelloWorld {
    public static void main(String[] args) throws Exception {
        System.out.println("Hello. Simple Web Server.");

        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/", indexHandler);
        server.createContext("/hello", mHelloHandler);
        server.setExecutor(null);
        server.start();
    }

    private static HttpHandler indexHandler = new HttpHandler() {

        @Override
        public void handle(HttpExchange httpExchange) throws IOException {
            byte[] response = "This is an index page.".getBytes();
            httpExchange.sendResponseHeaders(200, response.length);
            OutputStream os = httpExchange.getResponseBody();
            os.write(response);
            os.close();
        }
    };

    private static HttpHandler mHelloHandler = new HttpHandler() {

        @Override
        public void handle(HttpExchange httpExchange) throws IOException {
            byte[] response = "Hello. SnowDeer!!".getBytes();
            httpExchange.sendResponseHeaders(200, response.length);
            OutputStream os = httpExchange.getResponseBody();
            os.write(response);
            os.close();
        }
    };
}

 

* 참고

- HelloWorld.java 출처 : https://github.com/snowdeer/openshift-java-sample/blob/master/app/SimpleWebServer.java

                                      본 매뉴얼에서는 출처의 내용중 클래스 이름을 변경하여 적용하였음

 

빌드하기 위해 Dockerfile 을 작성합니다.

# vi Dockerfile

FROM openjdk:latest
COPY ./source/ /app/
WORKDIR /app
EXPOSE 8080
RUN javac HelloWorld.java
CMD ["java", "HelloWorld"]

 

* 설명

FROM python:latest : Golang 의 최신 버전을 기반으로 하는 Docker 이미지를 사용합니다.

COPY ./source/ /app/ : 호스트의 ./source/ 디렉토리에 있는 파일들을 컨테이너 내부의 /app/ 디렉토리로 복사합니다.
WORKDIR /app : 이후 명령어들이 실행될 디렉토리를 /app 으로 설정합니다.

EXPOSE 8080 : 컨테이너가 사용할 포트 번호를 설정합니다.

RUN javac HelloWorld.java : HelloWorld.java 소스 파일을 컴파일 하여 실행 가능한 파일을 생성합니다.

CMD ["java", "HelloWorld"] : 컨테이너가 시작될 때 실행할 명령을 설정합니다. 여기서는 java 명령으로 HelloWorld 파일을 실행합니다.

 

빌드 실행하면 새로운 도커 이미지가 생성됩니다.

# docker build -t java-app .

# docker images |grep java-app
node-app                          latest            928efdd8d2cb        2 minutes ago       326 MB

 

로컬의 도커 이미지를 Registry 에 올려놓고 사용하도록 합니다.

# docker tag java-app default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/java
# docker push default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/java

 

java 응용프로그램을 배포하기 위해 yaml 파일을 작성합니다.

# vi app.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: java-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: java-sample
  template:
    metadata:
      labels:
        app: java-sample
    spec:
      containers:
      - name: java-sample
        image: default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/java
        ports:
        - containerPort: 8080
          protocol: TCP
      imagePullSecrets:
      - name: sysdocu
---
apiVersion: v1
kind: Service
metadata:
  name: java-sample
spec:
  type: LoadBalancer
  selector:
    app: java-sample
  ports:
    - name: java-sample
      protocol: TCP
      port: 8080
      targetPort: 8080

 

위 yaml 파일을 적용면 deployment, pod, service, endpoint 가 모두 생성됩니다.

# oc apply -f app.yaml
deployment.apps/java-sample created
service/java-sample created

 

추가로 외부에서 접근이 가능하도록 route 를 생성합니다.

# oc expose service java-sample --name=java-sample --port=8080

route.route.openshift.io/java-sample exposed

 

생성된 route 정보를 확인합니다.

# oc get route
NAME            HOST/PORT                                    PATH   SERVICES        PORT   TERMINATION   WILDCARD
go-sample       go-sample-project412.apps.az1.sysdocu.kr              go-sample       8888                 None
java-sample     java-sample-project412.apps.az1.sysdocu.kr            java-sample     8080                 None
nodejs-sample   nodejs-sample-project412.apps.az1.sysdocu.kr          nodejs-sample   8080                 None
python-sample   python-sample-project412.apps.az1.sysdocu.kr          python-sample   8080                 None

 

외부에서 호스트명을 이용해 컨테이너 접근이 가능합니다.

# curl java-sample-project412.apps.az1.sysdocu.kr
This is an index page.

 

 

6. Ruby 배포

 

빌드할때 필요한 Gemfile, config.ru 파일을 미리 준비해 둡니다.

# mkdir ./source

# vi ./source/Gemfile

source 'https://rubygems.org'
gem 'rack'
gem 'puma'

 

# vi ./source/config.ru

require 'rack/lobster'

map '/health' do
  health = proc do |env|
    [200, { "Content-Type" => "text/html" }, ["1"]]
  end
  run health
end

map '/lobster' do
  run Rack::Lobster.new
end

map '/headers' do
  headers = proc do |env|
    [200, { "Content-Type" => "text/plain" }, [
      env.select {|key,val| key.start_with? 'HTTP_'}
      .collect {|key, val| [key.sub(/^HTTP_/, ''), val]}
      .collect {|key, val| "#{key}: #{val}"}
      .sort
      .join("\n")
    ]]
  end
  run headers
end

map '/' do
  welcome = proc do |env|
    [200, { "Content-Type" => "text/html" }, [<<WELCOME_CONTENTS
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Welcome to OpenShift</title>


<style>

/*!
 * Bootstrap v3.0.0
 *
 * Copyright 2013 Twitter, Inc
 * Licensed under the Apache License v2.0
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Designed and built with all the love in the world @twitter by @mdo and @fat.
 */

  .logo {
    background-size: cover;
    height: 58px;
    width: 180px;
    margin-top: 6px;
    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkwAIYBQAAeQF6gEsgPwAAAABJRU5ErkJggg==);
  }
.logo a {
  display: block;
  width: 100%;
  height: 100%;
}
*, *:before, *:after {
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
aside,
footer,
header,
hgroup,
section{
  display: block;
}
body {
  color: #404040;
  font-family: "Helvetica Neue",Helvetica,"Liberation Sans",Arial,sans-serif;
  font-size: 14px;
  line-height: 1.4;
}

html {
  font-family: sans-serif;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
}
ul {
    margin-top: 0;
}
.container {
  margin-right: auto;
  margin-left: auto;
  padding-left: 15px;
  padding-right: 15px;
}
.container:before,
.container:after {
  content: " ";
  /* 1 */

  display: table;
  /* 2 */

}
.container:after {
  clear: both;
}
.row {
  margin-left: -15px;
  margin-right: -15px;
}
.row:before,
.row:after {
  content: " ";
  /* 1 */

  display: table;
  /* 2 */

}
.row:after {
  clear: both;
}
.col-sm-6, .col-md-6, .col-xs-12 {
  position: relative;
  min-height: 1px;
  padding-left: 15px;
  padding-right: 15px;
}
.col-xs-12 {
  width: 100%;
}

@media (min-width: 768px) {
  .container {
    width: 750px;
  }
  .col-sm-6 {
    float: left;
  }
  .col-sm-6 {
    width: 50%;
  }
}

@media (min-width: 992px) {
  .container {
    width: 970px;
  }
  .col-md-6 {
    float: left;
  }
  .col-md-6 {
    width: 50%;
  }
}
@media (min-width: 1200px) {
  .container {
    width: 1170px;
  }
}

a {
  color: #069;
  text-decoration: none;
}
a:hover {
  color: #EA0011;
  text-decoration: underline;
}
hgroup {
  margin-top: 50px;
}
footer {
    margin: 50px 0 25px;
}
h1, h2, h3 {
  color: #000;
  line-height: 1.38em;
  margin: 1.5em 0 .3em;
}
h1 {
  font-size: 25px;
  font-weight: 300;
  border-bottom: 1px solid #fff;
  margin-bottom: .5em;
}
h1:after {
  content: "";
  display: block;
  width: 100%;
  height: 1px;
  background-color: #ddd;
}
h2 {
  font-size: 19px;
  font-weight: 400;
}
h3 {
  font-size: 15px;
  font-weight: 400;
  margin: 0 0 .3em;
}
p {
  margin: 0 0 2em;
  text-align: justify;
}
p + h2 {
  margin-top: 2em;
}
html {
  background: #f5f5f5;
  height: 100%;
}
code {
  background-color: white;
  border: 1px solid #ccc;
  padding: 1px 5px;
  color: #888;
}
pre {
  display: block;
  padding: 13.333px 20px;
  margin: 0 0 20px;
  font-size: 13px;
  line-height: 1.4;
  background-color: #fff;
  border-left: 2px solid rgba(120,120,120,0.35);
  white-space: pre;
  white-space: pre-wrap;
  word-break: normal;
  word-wrap: break-word;
  overflow: auto;
  font-family: Menlo,Monaco,"Liberation Mono",Consolas,monospace !important;
}

</style>

</head>
<body>

<section class='container'>
          <hgroup>
            <h1>Welcome to your Ruby application on OpenShift</h1>
          </hgroup>


        <div class="row">
          <section class='col-xs-12 col-sm-6 col-md-6'>
            <section>
              <h2>Deploying code changes</h2>
                <p>
                  The source code for this application is available to be forked from the <a href="https://www.github.com/sclorg/ruby-ex">OpenShift GitHub repository</a>.
                  You can configure a webhook in your repository to make OpenShift automatically start a build whenever you push your code:
                </p>

<ol>
  <li>From the Web Console homepage, navigate to your project</li>
  <li>Click on Browse &gt; Builds</li>
  <li>From the view for your Build click on the button to copy your GitHub webhook</li>
  <li>Navigate to your repository on GitHub and click on repository settings &gt; webhooks</li>
  <li>Paste your webhook URL provided by OpenShift &mdash; that's it!</li>
</ol>
<p>After you save your webhook, if you refresh your settings page you can see the status of the ping that Github sent to OpenShift to verify it can reach the server.</p>
<p>Note: adding a webhook requires your OpenShift server to be reachable from GitHub.</p>

                <h3>Working in your local Git repository</h3>
                <p>If you forked the application from the OpenShift GitHub example, you'll need to manually clone the repository to your local system. Copy the application's source code Git URL and then run:</p>

<pre>$ git clone &lt;git_url&gt; &lt;directory_to_create&gt;

# Within your project directory
# Commit your changes and push to OpenShift

$ git commit -a -m 'Some commit message'
$ git push</pre>

<p>After pushing changes, you'll need to manually trigger a build if you did not setup a webhook as described above.</p>
      </section>
          </section>
          <section class="col-xs-12 col-sm-6 col-md-6">

                <h2>Managing your application</h2>

                <p>Documentation on how to manage your application from the Web Console or Command Line is available at the <a href="http://docs.okd.io/latest/dev_guide/overview.html">Developer Guide</a>.</p>

                <h3>Web Console</h3>
                <p>You can use the Web Console to view the state of your application components and launch new builds.</p>

                <h3>Command Line</h3>
                <p>With the <a href="http://docs.okd.io/latest/cli_reference/overview.html">OpenShift command line interface</a> (CLI), you can create applications and manage projects from a terminal.</p>

                <h2>Development Resources</h2>
                  <ul>
                    <li><a href="http://docs.okd.io/latest/welcome/index.html">OpenShift Documentation</a></li>
                    <li><a href="https://github.com/openshift/origin">Openshift Origin GitHub</a></li>
                    <li><a href="https://github.com/openshift/source-to-image">Source To Image GitHub</a></li>
                    <li><a href="http://docs.okd.io/latest/using_images/s2i_images/ruby.html">Getting Started with Ruby on OpenShift</a></li>
                    <li><a href="http://stackoverflow.com/questions/tagged/openshift">Stack Overflow questions for OpenShift</a></li>
                    <li><a href="http://git-scm.com/documentation">Git documentation</a></li>
                  </ul>


          </section>
        </div>

        <footer>
          <div class="logo"><a href="https://www.openshift.com/"></a></div>
        </footer>
</section>


</body>
</html>
WELCOME_CONTENTS
    ]]
  end
  run welcome
end

 

* 참고

Ruby 의 경우 Rack 애플리케이션을 실행하는 데 사용되는 파일 이름이 config.ru 입니다.

Rack 은 Ruby 웹 프레임워크에서 사용되는 인터페이스로써 HTTP 요청을 처리하고 응답을 생성하기 위한 메소드들을 제공합니다.

따라서 Ruby 애플리케이션을 컨테이너에서 실행할 때, 컨테이너는 config.ru 파일을 찾아서 Rack 애플리케이션을 실행합니다.

- config.ru 출처 : https://github.com/sclorg/ruby-ex/blob/master/config.ru

                          본 매뉴얼에서는 출처의 내용중 background-image 부분만 짧은 (용량이 적은) 내용으로 변경하여 적용하였음

 

빌드하기 위해 Dockerfile 을 작성합니다.

Ruby 에서 사용하는 프레임워크는 sinatra 를 선택하였고 아래 예제는 sinatra 설치를 포함하고 있습니다.

# vi Dockerfile

FROM ruby:latest
COPY ./source/ /app/
WORKDIR /app
RUN bundle install
RUN gem install rack
RUN gem install sinatra -v 3.0.6
EXPOSE 8080
CMD ["rackup", "-o", "0.0.0.0", "-p", "8080"]

 

* 설명

FROM ruby:latest : Ruby의 최신 버전을 기반으로 하는 Docker 이미지를 사용합니다.
COPY ./source/ /app/ : 호스트의 ./source/ 디렉토리에 있는 파일들을 컨테이너 내부의 /app/ 디렉토리로 복사합니다.
WORKDIR /app : 이후 명령어들이 실행될 디렉토리를 /app 으로 설정합니다.
RUN bundle install : /app 디렉토리에서 Gemfile 에 명시된 Ruby 패키지를 설치합니다.
RUN gem install rack : Rack 웹 서버를 사용하기 위한 Ruby 패키지 rack 을 설치합니다.
RUN gem install sinatra -v 3.0.6 : Sinatra 웹 프레임워크의 특정 버전인 3.0.6 을 설치합니다.
                                                       Sinatra 는 간단한 Ruby 웹 애플리케이션을 빠르고 쉽게 작성할 수 있게 도와주는 경량 웹 프레임워크 입니다.

                                                       (출시 버전 정보 : https://rubygems.org/gems/sinatra/versions/)

EXPOSE 8080 : 컨테이너의 8080 포트를 오픈합니다.
CMD ["rackup", "-o", "0.0.0.0", "-p", "8080"] : 컨테이너가 시작될 때 /app 디렉토리에서 config.ru 를 찾아 Rack 애플리케이션을 실행하고,

                                                                       호스트의 8080 포트와 컨테이너 내부의 8080 포트를 연결합니다.

 

빌드 실행하면 새로운 도커 이미지가 생성됩니다.

# docker build -t ruby-app .

# docker images |grep ruby-app
ruby-app                          latest            24f660cbf71f       5 minutes ago      915 MB

 

로컬의 도커 이미지를 Registry 에 올려놓고 사용하도록 합니다.

# docker tag ruby-app default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/ruby
# docker push default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/ruby

 

ruby 응용프로그램을 배포하기 위해 yaml 파일을 작성합니다.

# vi app.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ruby-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ruby-sample
  template:
    metadata:
      labels:
        app: ruby-sample
    spec:
      containers:
      - name: ruby-sample
        image: default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/ruby
        ports:
        - containerPort: 8080
          protocol: TCP
      imagePullSecrets:
      - name: sysdocu

---
apiVersion: v1
kind: Service
metadata:
  name: ruby-sample
spec:
  type: LoadBalancer
  selector:
    app: ruby-sample
  ports:
    - name: ruby-sample
      protocol: TCP
      port: 8080
      targetPort: 8080

 

* 참고

imagePullSecrets 에 sysdocu 는 registry 접근 권한을 가진 사용자 입니다.

secret 생성 방법은 다른 포스트를 참고해주세요. ( https://sysdocu.tistory.com/1776 )

 

위 yaml 파일을 적용면 deployment, pod, service, endpoint 가 모두 생성됩니다.

# oc apply -f app.yaml
deployment.apps/ruby-sample created
service/ruby-sample created

 

추가로 외부에서 접근이 가능하도록 route 를 생성합니다.

# oc expose service ruby-sample --name=ruby-sample --port=8080

route.route.openshift.io/ruby-sample exposed

 

생성된 route 정보를 확인합니다.

# oc get route
NAME            HOST/PORT                                    PATH   SERVICES        PORT   TERMINATION   WILDCARD
go-sample       go-sample-project412.apps.az1.sysdocu.kr              go-sample       8888                 None
java-sample     java-sample-project412.apps.az1.sysdocu.kr            java-sample     8080                 None
nodejs-sample   nodejs-sample-project412.apps.az1.sysdocu.kr          nodejs-sample   8080                 None
python-sample   python-sample-project412.apps.az1.sysdocu.kr          python-sample   8080                 None

ruby-sample     ruby-sample-project412.apps.az1.sysdocu.kr            ruby-sample     8080                 None

 

외부에서 호스트명을 이용해 컨테이너 접근이 가능합니다.

curl 명령으로 접근하면 html 코드가 많이 출력되기 때문에 브라우저로 완성된 페이지를 보는것이 좋습니다.

- 웹브라우저 접속 URL : ruby-sample-project412.apps.az1.sysdocu.kr

 

반응형

댓글()

Openshift 4.12.0 응용프로그램 배포 - 웹서버, 데이터베이스

리눅스/OpenShift|2023. 2. 17. 10:45
반응형

여기에서는 다양한 응용프로그램을 배포해보겠습니다.

배포하면서 겪는 오류 및 해결책을 포함하였습니다.

1. 웹서버 : Nginx, Httpd

2. DB : MySQL, Redis, MongoDB, PostgreSQL

 

개발 언어를 배포하는 방법은 다음 포스팅을 확인해주세요.

( https://sysdocu.tistory.com/1778 )

개발언어 : Java, Node.js, PHP, Python, Ruby, Go

 

 

[ 사전 작업 ]

 

1) 프로젝트 생성

응용프로그램을 배포하기 전에 프로젝트를 생성해 놓아야 합니다.

사용자를 만들고 프로젝트 생성 권한을 주어도 되지만

본 매뉴얼에서는 편의를 위해 관리자 (system:admin) 로 작업을 진행하도록 하겠습니다.

# export KUBECONFIG=/root/installation_directory/auth/kubeconfig

# oc whoami

system:admin

 

project412 라는 프로젝트를 생성합니다.

# oc new-project project412 --description="This is an example project" --display-name="Hello OpenShift"

 

생성한 프로젝트를 선택합니다.

# oc project project412

 

2) 컨테이너 관리 도구 준비

응용프로그램 생성 방식에는 여러 가지가 있습니다.

- Git 저장소를 통해 생성하는 방법

- Docker 이미지를 통해 생성하는 방법

- Template 를 통해 생성하는 방법

 

여기에서는 Docker image 를 받아서 응용프로그램으로 배포하는 방식을 설명하겠습니다.

Docker 패키지를 설치합니다.

# yum -y install docker

 

Docker 데몬이 가동되어 있어야 다운로드가 됩니다.

# systemctl enable docker

# systemctl start docker

 

3) registry.redhat.io 로그인

몇가지 응용프로그램 이미지를 다운로드하고 배포하기 위해서 redhat 에 가입 합니다.

registry.redhat.io 에서는 다양한 이미지를 제공하고 있으므로 종류 및 버전을 확인하고자 할 경우

레드햇 사이트나 회원 가입 후 로그인하여 명령어로 살펴 볼 수 있습니다.

가입 하였을 경우 아래와 같이 로그인 합니다.

# docker login registry.redhat.io
Username: <redhat 계정>
Password: 
Login Succeeded

 

 

1-1. Nginx 배포

 

1) Nginx 배포

nginx 최신 버전 이미지를 다운로드 합니다.
# docker pull docker.io/nginx:latest

 

Openshift 에서 응용프로그램 생성은 oc new-app 명령을 이용합니다.

이 방식을 사용하면 어플리케이션을 생성할때 Deployment 와 Service 가 함께 생성됩니다.

# oc new-app docker.io/nginx:latest

 

응용프로그램 생성이 잘 되었는지 확인합니다.

생성은 되었으나 사용할 준비가 되지 않았고, CrashLoopBackOff 상태인 것이 확인됩니다.

# oc get pods
NAME                     READY   STATUS             RESTARTS     AGE
nginx-5fb558b844-qg99g   0/1     CrashLoopBackOff   1 (2s ago)   9s

 

로그를 확인합니다.

# oc logs nginx-5fb558b844-qg99g

...

2023/02/16 23:04:53 [warn] 1#1: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
nginx: [warn] the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
2023/02/16 23:04:53 [emerg] 1#1: mkdir() "/var/cache/nginx/client_temp" failed (13: Permission denied)
nginx: [emerg] mkdir() "/var/cache/nginx/client_temp" failed (13: Permission denied)

 

디렉토리 생성 권한이 없어 에러가 출력되었습니다.

Pod, Deployment, Service 를 지우고, Security Context Constraints 권한을 부여한뒤 다시 설치합니다.

(팁 : deployment 를 먼저 삭제하면 pod 도 같이 삭제 됩니다)

# oc delete deployment nginx

# oc delete service nginx

oc adm policy add-scc-to-user anyuid -z default
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid added: "default"

 

다시 설치를 합니다.

# oc new-app docker.io/nginx:latest

 

nginx 가 정상 구동 된것을 확인하였습니다.

# oc get pods
NAME                     READY   STATUS    RESTARTS   AGE
nginx-5fb558b844-jp4jt   1/1     Running   0          11s

 

* 참고

설치가 다 되었으면 위에서 주었던 권한을 제거 해야 하지만 아래로 계속 응용프로그램 설치를 할 예정이므로

해당 권한은 제거하지 않고 방법만 알고 넘어가도록 하겠습니다.

oc adm policy remove-scc-from-user anyuid -z default
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid removed: "default"

 

2) 라우팅 설정

외부에서 컨테이너 접근이 가능하도록 포트를 노출시킵니다.

뒤에 --port 80 옵션을 주어 특정 포트로 허용할 수도 있습니다.

# oc expose service nginx
route.route.openshift.io/nginx exposed
# oc get route
NAME    HOST/PORT                               PATH   SERVICES   PORT   TERMINATION   WILDCARD
nginx   nginx-project412.apps.az1.sysdocu.kr           nginx      80-tcp               None

 

출력된 호스트명과 포트로 서비스 접근을 확인합니다.

# curl --head nginx-project412.apps.az1.sysdocu.kr

HTTP/1.1 200 OK
server: nginx/1.23.3
date: Thu, 16 Feb 2023 23:19:57 GMT
content-type: text/html
content-length: 615
last-modified: Tue, 13 Dec 2022 15:53:53 GMT
etag: "6398a011-267"
accept-ranges: bytes
set-cookie: 7769a7256fe4dda43770015df5735002=e53f29e3aa14fd7cc39e7efd9f5e8535; path=/; HttpOnly
cache-control: private

 

 

1-2. Httpd 배포

 

1) Httpd 배포

httpd 최신 버전 이미지를 다운로드하고 배포합니다.
# docker pull docker.io/httpd:latest

# oc new-app docker.io/httpd:latest

 

응용프로그램 생성이 잘 되었는지 확인합니다.

# oc get pods
NAME                    READY   STATUS    RESTARTS   AGE
httpd-c9ccd7f68-9h7cd   1/1     Running   0          2m19s

 

2) 라우팅 설정

외부에서 컨테이너 접근이 가능하도록 포트를 노출시킵니다.

# oc expose service httpd
route.route.openshift.io/httpd exposed
# oc get route
NAME    HOST/PORT                               PATH   SERVICES   PORT     TERMINATION   WILDCARD
httpd   httpd-project412.apps.az1.sysdocu.kr           httpd      80-tcp                 None

 

출력된 호스트명과 포트로 서비스 접근을 확인합니다.

# curl --head httpd-project412.apps.az1.sysdocu.kr
HTTP/1.1 200 OK
date: Wed, 22 Feb 2023 00:33:05 GMT
server: Apache/2.4.55 (Unix)
last-modified: Mon, 11 Jun 2007 18:53:14 GMT
etag: "2d-432a5e4a73a80"
accept-ranges: bytes
content-length: 45
content-type: text/html
set-cookie: e391fcb731862e31c8659df26b916428=7b084df01e88111b96161caf6bb84603; path=/; HttpOnly
cache-control: private

 

 

2-1. MySQL 배포

 

MySQL 최신 버전 이미지를 다운로드하고 배포합니다.
# docker pull docker.io/mysql:latest

 

어플리케이션 배포 전 필요한 환경 변수를 필수로 입력해야 합니다.

- 택일 : MYSQL_ROOT_PASSWORD
            MYSQL_ALLOW_EMPTY_PASSWORD
            MYSQL_RANDOM_ROOT_PASSWORD

# oc new-app docker.io/mysql:latest -e MYSQL_ROOT_PASSWORD=12345678

 

아래와 같이 정상으로 구동되는 것이 확인되었습니다.

# oc get pods
NAME                    READY   STATUS    RESTARTS   AGE
mysql-bd544cdb8-zt87x   1/1     Running   0          12s

 

접속 테스트를 해봅니다.

# oc rsh mysql-bd544cdb8-zt87x
sh-4.4# mysql -u root -p12345678
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.32 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

 

* 참고 : 컨테이너 환경 변수 확인

예) oc set env pod <Pod 이름> --list
# oc set env pod mysql-bd544cdb8-zt87x --list
# pods/mysql-bd544cdb8-zt87x, container mysql
MYSQL_ROOT_PASSWORD=12345678

 

 

2-2. Redis 배포

 

Redis 최신 버전 이미지를 다운로드하고 배포합니다.
# docker pull docker.io/redis:latest

# oc new-app docker.io/redis:latest

 

응용프로그램 생성이 잘 되었는지 확인합니다.

# oc get pods
NAME                    READY   STATUS    RESTARTS   AGE
redis-6b68567b5-2dgk6   1/1     Running   0          11s

 

접속 테스트를 해봅니다.

# oc rsh redis-6b68567b5-2dgk6
# redis-cli
127.0.0.1:6379> ping
PONG

 

 

2-3. MongoDB 배포

 

MongoDB 이미지를 다운로드합니다.

# docker pull registry.redhat.io/rhscl/mongodb-34-rhel7

 

어플리케이션 배포 전 필요한 환경 변수를 필수로 입력해야 합니다.

- 필수 : MONGODB_ADMIN_PASSWORD

- 선택 : MONGODB_USER
            MONGODB_PASSWORD
            MONGODB_DATABASE

# oc new-app -e MONGODB_ADMIN_PASSWORD=12345678 registry.redhat.io/rhscl/mongodb-34-rhel7

 

응용프로그램 생성이 잘 되었는지 확인합니다.

# oc get pods
NAME                                READY   STATUS    RESTARTS   AGE
mongodb-34-rhel7-7477746d74-fdsjb   1/1     Running   0          45s


접속 테스트를 해봅니다.

# oc rsh mongodb-34-rhel7-7477746d74-fdsjb
sh-4.2$ mongo
MongoDB shell version v3.4.9
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.9
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
http://docs.mongodb.org/
Questions? Try the support group
http://groups.google.com/group/mongodb-user

 

 

2-4. PostgreSQL 배포

 

PostgreSQL 이미지를 다운로드합니다.

# docker pull registry.redhat.io/rhscl/postgresql-95-rhel7

 

어플리케이션 배포 전 필요한 환경 변수를 필수로 입력해야 합니다.

- 필수 : POSTGRESQL_ADMIN_PASSWORD 

- 선택 : POSTGRESQL_USER

            POSTGRESQL_PASSWORD

            POSTGRESQL_DATABASE

# oc new-app -e POSTGRESQL_ADMIN_PASSWORD=12345678 registry.redhat.io/rhscl/postgresql-95-rhel7

 

응용프로그램 생성이 잘 되었는지 확인합니다.

# oc get pods
NAME                                   READY   STATUS    RESTARTS   AGE
postgresql-95-rhel7-8666588965-tf28s   1/1     Running   0          16s


접속 테스트를 해봅니다.

# oc rsh postgresql-95-rhel7-8666588965-tf28s
sh-4.2$ psql        
psql (9.5.14)
Type "help" for help.

postgres=# \q

sh-4.2$

 

 

 

 

반응형

댓글()

Openshift 4.12.0 내부 이미지 레지스트리 (Private Image Registry) 구성

리눅스/OpenShift|2023. 2. 16. 13:43
반응형

본 문서는 공식 Documents 를 참고하여 작성하였습니다.

https://docs.openshift.com/container-platform/4.12/registry/index.html

 

Registry 를 구축, 운영하는 이유는 OCP 서버 내부 용량이 적어서 docker 이미지를 저장할 공간이 부족할때 사용하거나

보통은 어디에서든 접근이 가능하도록 Docker hub 처럼 서비스 하려는 목적이 있습니다.

아래는 Docker 이미지를 OpenShift 내부 Docker 레지스트리에 업로드 하는 방법 입니다.

 

[요구사항]
- 클러스터 관리자 권한 필요
- 100G 이상의 용량이 필요

 

 

1. Registry 구성하기

 

1) registry Pod 확인

현재 사용자를 확인합니다.

# export KUBECONFIG=/root/installation_directory/auth/kubeconfig

# oc whoami

system:admin

 

registry Pod 가 있는지 확인합니다.

있을 경우에 구성을 할 필요가 없기 때문입니다.

# oc get pod -n openshift-image-registry -l docker-registry=default
No resources found in openshift-image-registry namespace.

 

2) 이미지 레지스트리 상태 변경

베어메탈과 같은 플랫폼에 클러스터를 구성하면 OpenShift Image Registry Operator 는 자체적으로 Removed 되어 구성됩니다.

그렇기 때문에 Image Registry 사용을 위해서는 클러스터 구성 후 managementState 를 Removed 에서 Managed 로 전환하도록 Image Registry Operator 구성을 편집해야 합니다.

 

configs.imageregistry.operator.openshift.io 구성을 바로 패치 (수정) 합니다.

# oc patch configs.imageregistry.operator.openshift.io cluster --type merge --patch '{"spec": {"managementState":"Managed"}}'

config.imageregistry.operator.openshift.io/cluster patched

 

변경된 레지스트리 구성을 확인합니다.

# oc get configs.imageregistry.operator.openshift.io/cluster -o yaml |grep managementState

    managementState: Managed

 

3) 디렉토리 설정

원래 이미지를 안전하게 저장하기 위해서는 Persistent Volume (별도 스토리지) 을 사용해야 하지만, 여기에서는 동작 여부만 확인하는 것이 목적이므로 이미지 레지스트리 스토리지를 빈 디렉터리로 설정하겠습니다.

레지스트리를 재기동 하면 모든 이미지가 없어지기 때문에 이 방법은 반드시 테스트 환경에서만 사용하여야 합니다.

아래와 같이 config파일에 emptyDir를 추가해주면 됩니다.

# oc patch configs.imageregistry.operator.openshift.io cluster --type merge --patch '{"spec":{"storage":{"emptyDir":{}}}}'

config.imageregistry.operator.openshift.io/cluster patched

 

* 참고

Image Registry Operator가 구성 요소를 초기화하기 전에 이 명령을 실행하면 명령이 실패하며 다음 오류가 발생합니다.
Error from server (NotFound): configs.imageregistry.operator.openshift.io "cluster" not found
몇 분 후에 명령을 다시 실행해 보세요.

 

4) 스토리지 클래스 및 PVC (PersistentVolumeClaim) 구성

사용할 스토리지 클래스 및 가용성 영역을 지정하는 yaml 파일을 생성합니다.

# vi storage_class.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: custom-csi-storageclass
provisioner: cinder.csi.openstack.org
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  availability: az1

 

metadata: name: 스토리지 클래스를 구분짓는 이름

parameters: availability: 클러스터 구성시 사용한 클러스터 이름

 

구성을 적용합니다.

# oc apply -f storage_class.yaml 
storageclass.storage.k8s.io/custom-csi-storageclass created

 

스토리지 클래스 및 openshift-image-registry 네임스페이스를 사용하는 PVC (영구 볼륨 클레임) 를 지정하는 yaml 파일을 생성합니다. 

# vi pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: csi-pvc-imageregistry
  namespace: openshift-image-registry
  annotations:
    imageregistry.openshift.io: "true"
spec:
  accessModes:
  - ReadWriteMany
  volumeMode: Filesystem
  resources:
    requests:
      storage: 100Gi
  storageClassName: custom-csi-storageclass

metadata: name: PVC 를 구분짓는 이름

metadata: namespace: PVC 를 생성할 프로젝트 위치

spec: accessModes: 영구 볼륨 클레임의 액세스 모드

* 참고 : accessMode 옵션 설명

- ReadWriteOnce : 하나의 노드에서만 읽고 쓸 수 있습니다. 즉, PVC를 마운트한 노드만 해당 볼륨에 읽고 쓸 수 있습니다.
- ReadWriteMany : 여러 노드에서 동시에 읽고 쓸 수 있습니다. 즉, 볼륨이 여러 노드에 마운트되어 있어도 모든 노드에서 동시에 읽고 쓸 수 있습니다.

두 개 이상의 노드로 고 가용성을 지원하는 이미지 레지스트리를 배포하려면 ReadWriteMany 액세스가 필요합니다.

 

spec: resources: requests: storage: 사용할 볼륨 크기

spec: resources: storageClassName: 위에서 생성한 스토리지 클래스 이름

 

구성을 적용합니다.

# oc apply -f pvc.yaml

persistentvolumeclaim/csi-pvc-imageregistry created

 

외부에서 사용이 가능하도록 route 노출 시킵니다.

# oc patch configs.imageregistry.operator.openshift.io/cluster --type=merge --patch '{"spec":{"defaultRoute":true}}' -n openshift-image-registry

config.imageregistry.operator.openshift.io/cluster patched

 

5) 상태 확인

Cluster Operator 상태를 확인합니다.

# oc get co image-registry

NAME             VERSION   AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
image-registry   4.12.0    False       False         True       4h34m   Available: Error: storage backend not configured...

 

또는 이렇게 출력될 수 있습니다.

# oc get co image-registry
NAME             VERSION   AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
image-registry   4.12.0    False       True          False      49s     Available: The deployment does not have available replicas...

 

하지만 시간이 지나면 정상적인 상태로 돌아옵니다.

# oc get co image-registry
NAME             VERSION   AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
image-registry   4.12.0    True        False         False      4h51m   

 

레지스트리 Pod 가 확인됩니다.

# oc get pod -n openshift-image-registry -l docker-registry=default
NAME                                              READY   STATUS      RESTARTS   AGE
image-registry-7d646777fb-s6bwk                   1/1     Running     0          2d18h

 

6) Certificate 생성

Image registry 에 접근하기 위해서 연결하려는 도메인에 인증서가 설치 되어 있어야 합니다.

무료 사용을 원하시면 Let's encrypt SSL 또는 StartSSL 을 추천합니다.

https://sysdocu.tistory.com/1781

 

본 매뉴얼에서는 Let's encrypt 로 SSL 인증서를 발급받았으며, 필요한 파일 두개를 아래 디렉토리로 옮겨놓았습니다.

 

# mkdir -p /opt/registry/certs

(발급 인증서 복사 부분 생략)

# ll /opt/registry/certs
합계 12
-rw-r--r--. 1 root root 5701  2월 20 16:15 fullchain1.pem
-rw-------. 1 root root 1704  2월 20 16:15 privkey1.pem

 

참고로 위 파일에서 fullchain1.pem 은 cert1.pem 파일과 chain1.pem 파일의 내용을 모두 포함한 파일입니다.

즉, 공개 SSL 인증서와 인증서 체인이 모두 포함되었습니다.

 

용도 별 디렉토리를 추가로 생성하고 필요한 파일을 복사합니다.

# cd /opt/registry

# mkdir auth data

* 참고

/opt/registry/auth : 계정정보 파일 위치

/opt/registry/certs : 도메인 인증서 위치

/opt/registry/data : 이미지 저장 위치

 

이미지 레지스트리 사용자 계정을 생성을 위해 패키지 설치 및 명령을 수행합니다.

# cd auth

# yum -y install httpd-tools

# htpasswd -c -b -B htpasswd sysdocu 12345678

Adding password for user sysdocu

# cd

 

7) image-registry 컨테이너 생성

docker 를 이용해 image-registry 컨테이너를 가동합니다.

# docker run --name image-registry --restart=always -p 5000:5000 \
    -v /opt/registry/auth:/auth \
    -v /opt/registry/certs:/certs \
    -v /opt/registry/data:/var/lib/registry \
    -e "REGISTRY_AUTH=htpasswd" \
    -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
    -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
    -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/fullchain1.pem \
    -e REGISTRY_HTTP_TLS_KEY=/certs/privkey1.pem \
    -e REGISTRY_COMPATIBILITY_SCHEMA1_ENABLED=true \
    -d docker.io/library/registry:2

17e3d6b238fcebc19ffd87d39634234e4cf4708b4470f1e5c9f1e5ebaeee5264

 

route 정보에서 이미지 레지스트리 URL 을 확인 할 수 있습니다.

# oc get route -n openshift-image-registry
NAME            HOST/PORT                                                     PATH   SERVICES         PORT    TERMINATION   WILDCARD
default-route   default-route-openshift-image-registry.apps.az1.sysdocu.kr           image-registry   <all>   reencrypt     None

 

htpasswd 명령으로 생성했던 계정으로 로그인해서 아래와 같은 결과가 나오면 성공입니다.

# curl -u sysdocu:12345678 https://default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/v2/_catalog
{"repositories":[]}

 

출력이 안될경우 인증서 문제인데, -k 옵션을 사용하여 인증서를 무시하고 결과를 확인 할 수 있으나

이경우는 추천하지 않으므로 올바른 도메인으로 SSL 인증서를 다시 발급받거나

위의 컨테이너 생성 단계에서 인증서 파일에 인증서 및 체인키가 함께 들어 있는지 확인합니다.

 

* 에러 발생

여기에서 인증서 관련 에러 출력 : curl: (60) Peer's Certificate issuer is not recognized.

시스템에서 신뢰하지 않는 SSL 인증서를 사용하는 원격 서버에 접속하려고 할 때 발생합니다.

즉, 인증서 발급자가 시스템에서 인식되지 않을 때 발생하는 오류입니다.

이를 무시하는 방법은 curl -k 옵션을 추가하면 쉽게 해결이 되지만 설정한 인증서에 인증서 Chain 이 빠졌는지 확인해보면 좋습니다.

 

* 에러 발생

여기에서 인증서 관련 에러 출력 : x509: certificate signed by unknown authority

참고 (계정 정책 허용) : https://docs.openshift.com/container-platform/4.12/registry/accessing-the-registry.html

이미지에 직접 로그인 할 수도 있습니다.

# docker login -u sysdocu -p 12345678 default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000
Login Succeeded!

 

* 에러 발생

여기에서 연결 실패 에러 출력 : curl: (7) Failed connect to default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000; 연결이 거부됨

컨테이너 생성이 되지 않아 포트가 열리지 않은 경우가 가장 큰데, docker ps -a 명령으로 registry ID 값을 확인하여 logs 를 살펴보면 단서를 찾을 수 있습니다.

# docker ps -a

# docker logs <컨테이너 생성시 출력된 ID 값>

컨테이너를 삭제하고 재생성이 필요한 경우는 아래와 같이 진행합니다.

# docker stop <컨테이너 ID>

# docker rm <컨테이너 ID>

 

* 에러 발생

여기에서 연결 실패 에러 출력 : curl: (51) Unable to communicate securely with peer: requested domain name does not match the server's certificate.

이 오류는 아래와 같은 경우에 발생합니다.

- 요청한 도메인 이름과 인증서의 CN (Common Name) 이 일치하지 않는 경우 (인증서가 *.sysdocu.kr 인지, 서브도메인까지 일치하는지 확인 필요)

- 인증서의 subjectAltName 에서 요청한 도메인 이름이 발견되지 않는 경우

인증서를 재발급 받거나 인증서에 subjectAltName을 추가하여 요청한 도메인 이름이 인증서에 포함되도록 합니다.

 

 

2. 실습하기 (이미지 업로드)

 

원격으로 apache 가 포함된 php 이미지를 받아 내부 image-registry 에 올려 보도록 하겠습니다.

먼저 도커 사이트에서 이미지를 다운로드 받습니다.

# docker pull docker.io/php:7.4-apache
Trying to pull repository docker.io/library/php ... 
7.4-apache: Pulling from docker.io/library/php
a603fa5e3b41: Pull complete 
c428f1a49423: Pull complete 
156740b07ef8: Pull complete 
fb5a4c8af82f: Pull complete 
25f85b498fd5: Pull complete 
9b233e420ac7: Pull complete 
fe42347c4ecf: Pull complete 
d14eb2ed1e17: Pull complete 
66d98f73acb6: Pull complete 
d2c43c5efbc8: Pull complete 
ab590b48ea47: Pull complete 
80692ae2d067: Pull complete 
05e465aaa99a: Pull complete 
Digest: sha256:c9d7e608f73832673479770d66aacc8100011ec751d1905ff63fae3fe2e0ca6d
Status: Downloaded newer image for docker.io/php:7.4-apache

 

내부 이미지 레지스트리에 사용할 주소를 만들기위해 project 를 확인합니다.

각 사용자마다 프로젝트가 다를 수 있으므로 구분하기 위해 프로젝트명을 활용하지만 공용으로 사용하는 명칭도 괜찮습니다.

# oc project
Using project "project412" on server "https://api.az1.sysdocu.kr:6443".

 

Registry 에 로그인을 하지 않았다면 업로드 권한이 없으므로 위에서 생성했던 secret 계정을 이용해 Registry 에 로그인을 합니다.

# docker login -u sysdocu -p 12345678 default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000
Login Succeeded

 

태그 설정을 합니다.

예) docker tag <docker images 명령으로 출력된 이미지명> <내부 리포지토리 주소/프로젝트명/이미지명:버전>

# docker tag docker.io/php:7.4-apache default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/php

 

내부 이미지 레지스트리로 업로드 합니다.

예) docker push <내부 리포지토리 주소/프로젝트명/이미지명:버전>

# docker push default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/php
The push refers to a repository [default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/php]
3d33242bf117: Pushed 
529016396883: Pushed 
5464bcc3f1c2: Pushed 
28192e867e79: Pushed 
d173e78df32e: Pushed 
0be1ec4fbfdc: Pushed 
30fa0c430434: Pushed 
a538c5a6e4e0: Pushed 
e5d40f64dcb4: Pushed 
44148371c697: Pushed 
797a7c0590e0: Pushed 
f60117696410: Pushed 
ec4a38999118: Pushed 
latest: digest: sha256:18b3497ee7f2099a90b66c23a0bc3d5261b12bab367263e1b40e9b004c39e882 size: 3035

 

* 이 부분에서 업로드 되지 않는 경우 체크 사항

- 마지막 행에 'no basic auth credentials' 에러 출력시 Registry 에 로그인을 했는지 확인

- docker ps -a 명령으로 Registry 컨테이너가 정상 가동 되고 있는지 확인

 

명령이 잘 실행 되었다면 이미지 리스트를 다시 확인합니다.

# curl -u sysdocu:12345678 https://default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/v2/_catalog

{"repositories":["project412/php"]}

 

이제 다음과 같은 형태로 어느 호스트에서든 Registry 에 올려진 Docker Image 를 사용할 수 있습니다. (포트 확인)

# docker pull default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/php

 

 

3. Pod 생성하기

 

내부 레지스트리에 올려진 php 이미지로 컨테이너 생성이 가능하지만 접근권한이 없어서 다운로드 (pull) 가 되지 않을것입니다.

secret 을 생성하여 Pod 생성시 내부 레지스트리에 접근 권한을 획득하면 됩니다.

secret 은 현재 위치인 접근자 프로젝트 (예제에서는 project412) 에서 생성하면 됩니다.

# oc create secret docker-registry sysdocu \
  --docker-username=sysdocu \
  --docker-password=12345678 \
  --docker-email=admin@sysdocu.kr \
  --docker-server=default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000

 

Dockerfile 을 이용하여 php:7.4-apache 이미지에 설정을 변경하고 개발 소스를 추가하는 방법으로 진행해 보겠습니다.

테스트용으로 PHP 소스 파일을 만듭니다.

# mkdir ./source

# vi ./source/index.php

<?php
echo "Good job";
?>

 

Dockerfile 을 아래 내용으로 만듭니다.

80 포트를 이미 사용중일때는 바인딩이 되지 않습니다.

그래서 컨테이너는 8080 포트로 열고 외부접근은 80 번으로 접근하도록 합니다.

# vi Dockerfile

FROM default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/php
COPY ./source/ /var/www/html/
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
RUN sed -i 's/Listen 80/Listen 8080/' /etc/apache2/ports.conf
EXPOSE 8080
CMD ["apache2ctl", "-D", "FOREGROUND"]

 

현재 디렉토리에 있는 Dockerfile 을 사용하여 Docker 이미지를 새로 만듭니다.

형식) docker build --tag <새 이미지 이름> <Dockerfile 위치>

# docker build --tag my-app5 .

 

새로운 이미지가 확인 되었습니다.

# docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
my-app5                              latest              b0aeb2e3c8af        42 seconds ago      453 MB

docker.io/php                        7.4-apache          20a3732f422b        4 months ago        453 MB

default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/php   latest        20a3732f422b     4 months ago     453 MB

 

이번에는 로컬에 만들어져 있는 my-app5 이미지를 내부 레지스트리에 올려놓고 그것을 이용해 Pod 를 생성해보겠습니다.

업로드 권한이 필요하므로 생성했던 secret 계정을 이용해 Registry 에 로그인을 합니다.

# docker login -u sysdocu -p 12345678 default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000
Login Succeeded

 

태그 설정을 하고 업로드 합니다.

# docker tag my-app5 default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/custom-php

# docker push default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/custom-php

 

Pod 생성을 위해 deployment yaml 파일을 만듭니다.

Deployment 는 Pod 를 생성, 업데이트, 롤백 및 모니터링하는데 사용되는 추상화 계층입니다.

Deployment 는 Pod 의 명세를 정의하며, 해당 명세를 기반으로 하나 이상의 파드를 자동으로 생성합니다.

아래 deployment 생성 후 Pod 를 삭제하면 다시 생성되는 것을 볼 수 있습니다. Pod 유지 개수는 replicas 값에 따라 결정 됩니다.

내부 레지스트리에 올려진 이미지 주소와 하단부에 imagePullSecret (위에서 만든 secret 이름) 을 잘 지정하여 작성합니다.

# vi deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app5  // Deployment 이름. Pod 이름도 같은 이름으로 생성되지만 뒤에 랜덤 문자가 붙음
spec:
  replicas: 1
  selector:
    matchLabels:
      app: l-app5  // 아래랑 같아야 함
  template:
    metadata:
      labels:
        app: l-app5  // 위랑 같아야 함
    spec:
      containers:
      - name: c-app5
        image: default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/custom-php
        ports:
        - containerPort: 8080
      imagePullSecrets:
      - name: sysdocu

 

Deployment 를 생성합니다.

# oc apply -f deployment.yaml

 

----- Deployment 생성시 Pod 가 함께 생성되었기 때문에 이 부분은 건너 뜁니다 -----

참고로 Deployment 없이 Pod 만 생성하는 방법입니다.

# vi pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: app5
spec:
  label:
    app: l-app5
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: c-app5
    image: default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/custom-php
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
      runAsNonRoot: true
  imagePullSecrets:
  - name: sysdocu

 

Pod 를 생성합니다.

# oc apply -f pod.yaml

-----------------------------------------------------------------------------------------------------------

 

Pod 가 생성된 것이 확인되었습니다.

# oc get pod
NAME   READY   STATUS    RESTARTS   AGE
app5   1/1     Running   0          3s

 

외부에서 접근하려면 service 와 endpoint, route 까지 생성해야 합니다.

명령 한줄로 service 와 endpoint 가 같이 생성됩니다.

형식) oc expose deployment <Deployment 이름> --type=LoadBalancer --name=<생성할 Service 이름>

# oc expose deployment app5 --type=LoadBalancer --name=s-app5

 

----- Service 생성시 이 부분은 건너 뜁니다 -----

# vi service.yaml

apiVersion: v1
kind: Service
metadata:
  name: s-app5
spec:
  type: LoadBalancer
  selector:
    app: l-app5
  ports:
    - name: p-app5
      protocol: TCP
      port: 80
      targetPort: 8080

 

* 설명

- type : 옵션이 없으면 클러스터 내부에서만 접속 가능한 Cluster-IP 가 부여되는데

            외부에서 접속 가능하게 하려면 NodePort 또는 LoadBalancer 로 설정 해야 합니다.

            ㄴ NodePort : 단일 사용자 및 단일 Pod 연결의 경우 NodePort 가 적절하며 개발 및 테스트 용도로 사용합니다.

                                   노드의 IP 와 포트로 직접 접근할 수 있으므로 보안이 취약합니다.

            ㄴ LoadBalancer : 다수의 Pod 제공이 가능하며 주로 프로덕션 환경에서 사용됩니다. 또한 안전하고 성능이 우수합니다.

- selector: app : 위에 app.yaml 에서 명시한 label 이름

- port : Service 가 노출하고 있는 포트

- targetPort : Service 가 연결하려는 백엔드 Pod 의 포트

 

위 yaml 파일을 적용합니다.

# oc apply -f service.yaml

------------------------------------------------------------

 

service 생성된것이 확인되었습니다.

# oc get service
NAME     TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)          AGE
s-app5   LoadBalancer   172.30.40.43   115.68.142.106   8080:30343/TCP   26s

 

route 를 생성하여 외부에서 접근 가능하도록 합니다.

형식) oc expose service <service 이름> --name=<생성할 route 이름> --port=<open 할 포트>

# oc expose service s-app5 --name=r-app5 --port=8080
route.route.openshift.io/r-app5 exposed

 

생성된 route 를 확인합니다.

# oc get route
NAME     HOST/PORT                             PATH   SERVICES   PORT   TERMINATION   WILDCARD
r-app5   r-app5-project412.apps.az1.sysdocu.kr          s-app5     8080                 None

 

외부에서 접근 테스트를 합니다.

# curl r-app5-project412.apps.az1.sysdocu.kr

Good job

 

[ oc new-app 명령어 사용하기 ]

위에 Pod, Service, Endpoint, Route 생성을 자동으로 도와주는 oc new-app 명령이 있습니다.

이 방법은 추후에 추가하도록 하겠습니다...

 

 

4. Registry 이미지 삭제하기

 

운영 서버 용량 부족이나 기타 사유로 인해 Registry 의 이미지를 지워야 할때나

같은 이름의 리포지토리가 있으면 push 를 해도 덮어씌우기가 적용되지 않고 그냥 넘어가므로 (pass) 기존 이미지를 지워야 할때가 있습니다.

그럴때 사용하는 명령인데, 올바른지 모르겠지만 아래와 같은 형식으로 명령을 실행했더니 에러가 출력되었습니다.

 

1) Registry 에서 삭제

예시) project412 에 올려놓은 ruby3 이미지 삭제

# oc adm prune images --registry-url=default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/ruby3 --confirm

error: you must use a client config with a token

 

하지만 더 간단한 방법이 있습니다.

위 예제를 따라서 구성하였다면 registry 에 올려진 이미지의 경로는 아래와 같습니다.

/opt/registry/data/docker/registry/v2/repositories/<프로젝트명>

 

registry 에 업로드 된 이미지를 확인해 봅니다.

# curl -u sysdocu:12345678 https://default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/v2/_catalog
{"repositories":["project412/custom-php","project412/custom-php2","project412/ruby","project412/ruby2","project412/ruby3"]}

 

로컬 디스크에 저장된 이미지를 확인합니다.

# ll /opt/registry/data/docker/registry/v2/repositories/project412
합계 0
drwxr-xr-x. 5 root root 55  4월  5 14:28 custom-php
drwxr-xr-x. 5 root root 55  4월 11 10:30 custom-php2
drwxr-xr-x. 5 root root 55  4월 18 09:12 ruby
drwxr-xr-x. 5 root root 55  4월 18 14:33 ruby2
drwxr-xr-x. 5 root root 55  4월 18 15:01 ruby3

 

시스템의 rm 명령을 이용하여 원하는 이미지 디렉토리를 삭제합니다.

# rm -rf /opt/registry/data/docker/registry/v2/repositories/project412/ruby3

 

지워진것이 확인되었습니다.

# curl -u sysdocu:12345678 https://default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/v2/_catalog
{"repositories":["project412/custom-php","project412/custom-php2","project412/ruby","project412/ruby2"]}

 

2) ImageStream 목록에서 삭제

이미지 Registry 를 재설치하거나 Repository 를 한개 삭제 했다 하더라도 이미지 스트림 목록에는 여전히 보입니다.

이미지 스트림은 Openshift 내부에서 관리되며, 클러스터 노드에서 호스팅되는 Docker 레지스트리와는 별개이기 때문입니다.

Openshift 의 Docker 이미지 레지스트리는 기본적으로 Docker V2 이미지 프로토콜을 지원하며, 이 레지스트리에는 이미지 스트림에서 참조하는 Docker 이미지가 저장됩니다.
정리하면, 이미지 스트림은 Openshift 서버 내에서 관리되며, 이미지는 Openshift의 Docker 이미지 레지스트리에 저장됩니다.

그렇기 때문에 Registry 에서도 삭제한 아이템을 ImageStream 에서도 따로 지워줘야 합니다.

 

# oc get is
NAME            IMAGE REPOSITORY                                                                    TAGS                UPDATED
custom-php      default-route-openshift-image-registry.apps.az1.sysdocu.kr/project412/custom-php      latest              8 days ago

custom-php2     default-route-openshift-image-registry.apps.az1.sysdocu.kr/project412/custom-php2     latest              8 days ago
ruby             default-route-openshift-image-registry.apps.az1.sysdocu.kr/project412/ruby            latest              7 days ago
ruby2            default-route-openshift-image-registry.apps.az1.sysdocu.kr/project412/ruby2           latest              8 days ago

ruby3            default-route-openshift-image-registry.apps.az1.sysdocu.kr/project412/ruby3           latest              8 days ago

 

# oc delete is ruby3

imagestream.image.openshift.io "ruby3" deleted

 

 

* 참고 : 외부 이미지를 Registry 로 바로 가져오기

 

외부 Registry 의 이미지를 내부 Registry 로 가져오는 방법 (import-image) 입니다.

import-image 를 이용하여 가져온 이미지는 docker images 로는 이미지가 보이지 않으며 oc get is 명령으로 이미지 확인이 가능합니다.

형식) oc import-image <이미지 스트림 이름> --from=<원본 이미지 경로> --confirm

# oc import-image my-image --from=default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/library/my-app5 --confirm

 

이번엔 Docker Hub 에서 한 가지 더 받아보겠습니다.

# oc import-image test --from=docker.io/nginx --confirm

 

특이사항은 등록 상태를 확인해보면 현재의 project 명 (project412) 과 이미지 스트림 이름으로 서비스 주소가 완성 되어진 것이 보입니다.

그렇기 때문에 이미지 스트림 이름을 명확히 해야 추후 이미지를 이용할때 혼동되지 않습니다.

# oc get is

NAME       IMAGE REPOSITORY                                                                 TAGS     UPDATED
my-image   default-route-openshift-image-registry.apps.az1.sysdocu.kr/project412/my-image   latest   

test             default-route-openshift-image-registry.apps.az1.sysdocu.kr/project412/test             latest

 

 

* 참고 : 내부 이미지 레지스트리 삭제

 

1) PVC 삭제

서비스 중지 또는 재설치 등의 이유로 Registry 를 재설치할 경우 아래와 같은 절차를 따릅니다.

모든 프로젝트에서 PVC 를 확인하면 image registry pvc 가 어느 네임스페이스에 있는지 확인이 됩니다.

# oc get pvc -A
NAMESPACE                  NAME                    STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS              AGE
openshift-image-registry   csi-pvc-imageregistry   Pending                                      custom-csi-storageclass   4d21h

 

이름과 네임스페이스를 이용하여 pvc 정보를 삭제합니다.

# oc delete pvc csi-pvc-imageregistry -n openshift-image-registry
persistentvolumeclaim "csi-pvc-imageregistry" deleted

 

2) Pod 삭제

openshift-image-registry 프로젝트 (네임스페이스) 에서 이미지 레지스트리 Pod 이름을 확인합니다.

# oc get pod -n openshift-image-registry -l docker-registry=default
NAME                              READY   STATUS    RESTARTS   AGE
image-registry-6dfb9c8f99-xbzbd   1/1     Running   0          4d21h

 

프로젝트명을 명시하여 해당 이미지 레지스트리 Pod 를 삭제하면 곧바로 다른 이름으로 되살아납니다.
# oc delete pod image-registry-6dfb9c8f99-xbzbd -n openshift-image-registry
pod "image-registry-6dfb9c8f99-xbzbd" deleted

 

그렇기에 완전히 삭제를 원할 경우 이미지 레지스트리 구성의 상태값에 Removed 로 표기를 해야 합니다.

# oc patch configs.imageregistry.operator.openshift.io cluster -p '{"spec":{"managementState":"Removed"}}' --type='merge'
config.imageregistry.operator.openshift.io/cluster patched

 

Terminating 상태이므로 시간이 지나 삭제 됩니다.

# oc get pod -n openshift-image-registry -l docker-registry=default
NAME                                              READY   STATUS        RESTARTS        AGE
image-registry-6dfb9c8f99-r89vg                   1/1     Terminating   0               10m

 

삭제 되었습니다.

# oc get pod -n openshift-image-registry -l docker-registry=default
No resources found in openshift-image-registry namespace.

 

3) Docker 컨테이너 및 이미지 삭제

컨테이너 ID 를 확인하고 중지, 삭제합니다.

# docker ps
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
46d0300f61f6        docker.io/library/registry:2   "/entrypoint.sh /e..."   37 minutes ago      Up 37 minutes       0.0.0.0:5000->5000/tcp   image-registry

 

# docker stop 46d0300f61f6
46d0300f61f6

 

# docker rm 46d0300f61f6
46d0300f61f6

 

끝으로 Docker 이미지를 저장하였던 폴더를 비워줍니다.

# rm -rf /opt/registry/data/*

 

 

* 참고 : SSL 인증서 업데이트

 

SSL 인증서 사용기간이 정해져있어 만료일이 도래하기 전에 인증서 업데이트를 해주어야 정상적인 서비스가 가능하며, 인증서를 교체하지 않고 기간이 만료된 경우 Pod 생성 에러 'Failed to pull image' 가 발생됩니다.

 

Registry 인증서가 있는 디렉토리로 이동합니다.

# cd /opt/registry/certs
# ll
합계 20
-rw-r--r-- 1 root root 1960  3월 20 10:55 cert1.pem
-rw-r--r-- 1 root root 3749  3월 20 10:55 chain1.pem
-rw-r--r-- 1 root root 5709  3월 20 10:55 fullchain1.pem
-rw------- 1 root root 1704  3월 20 10:55 privkey1.pem

 

갱신한 SSL 인증서를 아래와 같이 덮어씌워 교체 하였습니다. (교체 방법 생략)

참조 : https://sysdocu.tistory.com/1546

# ll
합계 40
-rw-r--r-- 1 root root 2232  6월 23 08:48 cert1.pem
-rw-r--r-- 1 root root 3749  6월 23 08:48 chain1.pem
-rw-r--r-- 1 root root 5981  6월 23 08:48 fullchain1.pem
-rw------- 1 root root 3272  6월 23 08:48 privkey1.pem

 

Let's encrypt 로 생성했다면 필요한 파일은 fullchain1.pem 과 privkey1.pem 두개 입니다.

docker 를 이용해 image-registry 컨테이너를 재가동합니다. 재가동 방식은 기존 image-registry 컨테이너를 삭제하고 재생성 하는 방법입니다.

# docker ps
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
d1fba07b40ef        docker.io/library/registry:2   "/entrypoint.sh /e..."   6 weeks ago         Up 6 weeks          0.0.0.0:5000->5000/tcp   image-registry

 

출력된 CONTAINER ID 를 이용하여 image-registry 컨테이너를 중지하고 삭제합니다.

# docker stop d1fba07b40ef
d1fba07b40ef

# docker rm d1fba07b40ef
d1fba07b40ef

 

다시 생성합니다.

# docker run --name image-registry --restart=always -p 5000:5000 \
    -v /opt/registry/auth:/auth \
    -v /opt/registry/certs:/certs \
    -v /opt/registry/data:/var/lib/registry \
    -e "REGISTRY_AUTH=htpasswd" \
    -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
    -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
    -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/fullchain1.pem \
    -e REGISTRY_HTTP_TLS_KEY=/certs/privkey1.pem \
    -e REGISTRY_COMPATIBILITY_SCHEMA1_ENABLED=true \
    -d docker.io/library/registry:2

1c3e5d916cf9665658e9828db7ca973062dc4fa4bdc8fb2a0d5b9fe110883045

 

이제 Registry 이미지가 잘 다운로드 되는지 테스트 하는 방법은 Pod 를 직접 생성해본다거나, 다음과 같이 docker images 로 출력되는 REPOSITORY 중에 하나를 받아보는 것입니다.

# docker pull default-route-openshift-image-registry.apps.az1.sysdocu.kr:5000/project412/python

 

반응형

댓글()