3 Oct 2020

[Project] DevSecOps 프로젝트 - 개요 및 환경 구성

목차

1. 프로젝트 개요

   1.1. 프로젝트 소개

   1.2. 프로젝트 목적

   1.3. 프로젝트 아키텍처

2. 환경 구성

   2.1. GCP(Google Cloud Platform) 구성

   2.2. 쿠버네티스 구성

   2.3. GitLab 구성

   2.4. Jenkins 구성


1. 프로젝트 개요


1.1. 프로젝트 소개

DevSecOps란 DevOps에 보안 즉, Security를 접목시킨 용어로 소프트웨어 개발과 운영을 접합한 소프트웨어 개발 라이프 사이클에 보안을 내장한 개발 프로세스이다.

많은 기업들이 클라우드 환경으로의 디지털 트랜스포메이션을 진행하면서, 기존의 보안 쳬계와 다른 클라우드 네이티브 환경에서의 보안을 어렵게 생각하는 경우가 많다.

온프레미스와는 전혀 다른 방식으로 보안을 구현해야하는 필요성이 대두되었고, 클라우드 환경에서는 기업마다 다른 개발환경과 개발문화로 인한 변화의 폭이 훨씬 크므로, 자동화되고 가시화된 개발보안방법론인 DevSecOps 쳬계를 적용하므로써 빠르게 변화하는 서비스 개발 속도에 발맞춰 보안의 속도도 따라가야 할 필요가 있다.


1.2. 프로젝트 목적

본 프로젝트를 통해 클라우드 네이티브 환경에서 DevSecOps 체계를 구현하는데 필요한 툴 체인들을 이해하고, 보안이 적용된 다양한 툴 체인 파이프라인 방법들을 구축하고자 한다.

클라우드 네이티브 환경에서의 DevSecOps 쳬계와 CI/CD 체계를 이해하고, 이 체계에 적용되는 보안 분석 도구들을 구축하여 실제 서비스 개발 프로세스에 적용할 수 있는 DevSecOps 툴체인 파이프라인 구축하는 것이 목적이다.


1.3. 프로젝트 아키텍처

본 프로젝트의 아키텍처는 다음 [그림 1]과 같다.

devops_architecture
[그림 1] DevOps 아키텍처


GitLab, Jenkins, Harbor, Helm을 이용해 CI(Continuous Integration)를, Jenkins, ArgoCD, Kubernetes를 통해 CD(Continuous Delivery/Continuous Deployment) 환경을 구축하고 파이프라인 실행 결과를 Slack으로 전달한다.

그리고 서비스 운영에 필요한 로깅 시스템은 EFK(Elasticsearch-Fluentd-Kibana)를, 모니터링 시스템은 Prometheus, Grafana를 쿠버네티스의 서비스 형태로 구축했다.


2. 환경 구성


2.1. GCP(Google Cloud Platform) 구성

GCP의 기능으로 있는 GKE(Google Kubernetes Engine)을 사용하여 손 쉽게 쿠버네티스 환경을 구성할 수 있지만, 현재는 테스트용으로 인스턴스를 따로 생성하여 kubeadm을 통해 쿠버네티스를 구성할 것이다.

따라서 다음 [그림 2]와 같이 GCP 인스턴스를 생성해준다.

gcp_instance
[그림 2] GCP 인스턴스 생성


인스턴스 생성할 때의 설정은 다음과 같다.

  • 리전: asia-northeast3(서울)

  • 머신 계열: n1-standard-2(vCPU 2개, 7.5GB 메모리)

  • 부팅 디스크: CentOS 7

  • 액세스 범위: 모든 Cloud API에 대한 전체 액세스 허용

  • 방화벽: HTTP/HTTPS 트래픽 허용

추가로 [VPC 네트워크] - [외부 IP 주소] 탭에 들어가 각 인스턴스들의 IP를 고정으로 바꿔주었다.


2.2. 쿠버네티스 구성

위에서 언급하였듯 생성한 인스턴스에 kubeadm 명령을 통해 쿠버네티스를 구성한다.

먼저 kube-master, kube-worker1, kube-worker2 모두 동일하게 아래의 명령을 실행하도록 한다.

  • 쿠버네티스 설치를 위한 모든 과정은 root 권한으로 진행한다.

      $ sudo su -
    
  • 스왑 메모리의 사용을 중지한다.

      $ swapoff -a
      $ echo 0 > /proc/sys/vm/swappiness
      $ sed -e '/swap/ s/^#*/#/' -i /etc/fstab
    

    Swap은 디스크의 일부 공간을 메모리처럼 사용하는 기능이다. Kubelet이 정상 동작할 수 있도록 swap 디바이스와 파일 모두 disable 한다.

  • 각 노드의 통신을 원활하게 하기 위해 방화벽을 해제하고 SELinux를 비활성화 시켜준다.

      $ systemctl disable firewalld
      $ systemctl stop firewalld
    
      $ setenforce 0
      $ sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
    
  • RHEL과 CentOS 7에서 iptables 관련 이슈가 있어 커널 매개변수를 다음과 같이 수정하고 적용한다.

      $ cat <<EOF >  /etc/sysctl.d/k8s.conf
      net.bridge.bridge-nf-call-ip6tables = 1
      net.bridge.bridge-nf-call-iptables = 1
      EOF
    
      $ sysctl --system
    
  • br_netfilter 모듈을 활성화한다.

      $ modprobe br_netfilter
    

    br_netfilter 모듈을 명시적으로 추가한 후에 lsmod | grep br_netfilter 명령어로 추가 여부를 확인할 수 있다.

  • 도커 및 쿠버네티스 설치 후 서비스 시작

      $ yum -y update
      $ yum install docker -y
      $ systemctl enable docker && systemctl start docker.service
    
      $ cat <<EOF > /etc/yum.repos.d/kubernetes.repo
      [kubernetes]
      name=Kubernetes
      baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
      enabled=1
      gpgcheck=1
      repo_gpgcheck=1
      gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
      exclude=kube*
      EOF
    
      $ yum -y update
      $ yum install -y yum-utils device-mapper-persistent-data lvm2
      $ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 
      $ yum update && yum install docker-ce-18.06.2.ce
      $ yum install -y --disableexcludes=kubernetes kubeadm-1.15.5-0.x86_64 kubectl-1.15.5-0.x86_64 kubelet-1.15.5-0.x86_64
      $ systemctl enable kubelet && systemctl start kubelet
    

    본 프로젝트에서는 헬스체크 이슈, 대쉬보드 호환성의 이유로 비교적 안정적인 쿠버네티스 1.15 버전으로 명시하여 설치했다.

    처음에 1.19 버전으로 설치했었다가 쿠버네티스 클러스터의 매트릭을 수집해주는 kube-state-metrics이 정상적으로 작동하지 않아 1.15버전으로 재설치했다.


다음으로 kube-master에서만 아래의 명령을 실행하도록 한다.

  • 컨트롤 구성 요소들의 이미지를 설치한다.

      $ kubeadm config images pull
    
  • kube-master 노드를 초기화 해준다.

      $ kubeadm init
      [init] Using Kubernetes version: v1.15.5
      [preflight] Running pre-flight checks
              [WARNING Service-Kubelet]: kubelet service is not enabled, please run 'systemctl enable kubelet.service'
      [preflight] Pulling images required for setting up a Kubernetes cluster
      [preflight] This might take a minute or two, depending on the speed of your internet connection
      [preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
      [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
      [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
      [kubelet-start] Activating the kubelet service
      [certs] Using certificateDir folder "/etc/kubernetes/pki"
      [certs] Generating "ca" certificate and key
    
      ...(중략)
    
      Your Kubernetes control-plane has initialized successfully!
      To start using your cluster, you need to run the following as a regular user:
      mkdir -p $HOME/.kube
      sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
      sudo chown $(id -u):$(id -g) $HOME/.kube/config
      You should now deploy a pod network to the cluster.
      Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
      https://kubernetes.io/docs/concepts/cluster-administration/addons/
      Then you can join any number of worker nodes by running the following on each as root:
      kubeadm join 10.178.0.19:6443 --token tcqjev.d775p70aj3dseele \
          --discovery-token-ca-cert-hash sha256:d9f67f85ea3e5ca8821dd311cf0c3c886d9637c14814b61f15f67e06f732b200
    
  • kubeadm init의 결과에서 나온 것 처럼 일반 사용자가 kubectl 명령을 사용할 수 있도록 환경변수를 설정한다.

      $ mkdir -p $HOME/.kube
      $ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
      $ sudo chown $(id -u):$(id -g) $HOME/.kube/config
    


다음은 kubeadm init의 결과에서 나온 것 처럼 kube-worker 노드들에서만 아래의 명령을 실행하도록 한다.

  • 컨트롤 구성 요소들의 이미지를 설치한다.

      $ kubeadm join 10.178.0.19:6443 --token tcqjev.d775p70aj3dseele \
          --discovery-token-ca-cert-hash sha256:d9f67f85ea3e5ca8821dd311cf0c3c886d9637c14814b61f15f67e06f732b200
      [preflight] Running pre-flight checks
      [preflight] Reading configuration from the cluster...
      [preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubead
      m-config -oyaml'
      [kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.15" Con
      figMap in the kube-system namespace
      [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
      [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubead
      m-flags.env"
      [kubelet-start] Activating the kubelet service
      [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
      This node has joined the cluster:
      * Certificate signing request was sent to apiserver and a response was received.
      * The Kubelet was informed of the new secure connection details.
      Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
    


마지막으로 kube-master에서 weave를 설치한다.

  • kubectl apply를 통해 weave 생성

      $ kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
      serviceaccount/weave-net created
      clusterrole.rbac.authorization.k8s.io/weave-net created
      clusterrolebinding.rbac.authorization.k8s.io/weave-net created
      role.rbac.authorization.k8s.io/weave-net created
      rolebinding.rbac.authorization.k8s.io/weave-net created
      daemonset.apps/weave-net created
    

    CNI(Container Network Interface)를 설치하면 CoreDNS Pod가 정상적으로 동작하게 된다.


쿠버네티스 클러스터가 정상적으로 구성되었는지 확인해본다.

$ kubectl get nodes
NAME           STATUS   ROLES    AGE   VERSION
kube-master    Ready    master   1d   v1.15.5
kube-worker1   Ready    <none>   1d   v1.15.5
kube-worker2   Ready    <none>   1d   v1.15.5
$ kubectl get cs
NAME                 STATUS    MESSAGE             ERROR
controller-manager   Healthy   ok                  
scheduler            Healthy   ok                  
etcd-0               Healthy   {"health":"true"}
$ kubectl get po -o custom-columns=POD:metadata.name,NODE:spec.nodeName --sort-by spec.nodeName -n kube-system
POD                                   NODE
kube-proxy-ks9ss                      kube-master
etcd-kube-master                      kube-master
kube-apiserver-kube-master            kube-master
kube-controller-manager-kube-master   kube-master
kube-scheduler-kube-master            kube-master
weave-net-4g8fj                       kube-master
kube-proxy-mg5z5                      kube-worker1
weave-net-rjqk5                       kube-worker1
coredns-5c98db65d4-xd8lw              kube-worker1
coredns-5c98db65d4-zf5kx              kube-worker2
weave-net-gvwqv                       kube-worker2
kube-proxy-7jd9r                      kube-worker2


2.3. GitLab 구성

Gitlab과 Jenkins 설치는 [그림 2]의 servers 인스턴스에서 진행한다.

GitLab은 자체 CI를 무료로 제공하고 있어 외부 CI 서비스를 사용할 필요가 없다.

하지만 Jenkins에서 제공되는 플러그인이 많기 때문에 보안을 적용한 파이프라인을 구축하기에는 외부 CI/CD 서비스인 Jenkins를 사용할 것이고, 기존의 서비스 중인 Github 혹은 Gitlab을 사용해도 무방하다.

  • GitLab 설치 및 설정

      # openssh 설치
      $ sudo yum install -y curl policycoreutils-python openssh-server openssh-clients
    
      # 서버 부팅 시 sshd 실행
      $ sudo systemctl enable sshd && sudo systemctl start sshd
    
      # 방화벽 해제
      $ sudo firewall-cmd --permanent --add-service=http
      $ sudo firewall-cmd --permanent --add-service=https
      $ sudo firewall-cmd --reload
    
      # 메일서버 설치
      $ sudo yum install -y postfix
    
      # 서버 부팅 시 메일서버 실행
      $ sudo systemctl enable postfix && sudo systemctl start postfix
    
      # gitlab 패키치 저장소 등록 및 설치
      $ curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
      $ sudo EXTERNAL_URL="servers IP:8001" yum install -y gitlab-ce
      gitlab Reconfigured!
    
          *.                  *.
          ***                 ***
          *****               *****
          .******             *******
          ********            ********
      ,,,,,,,,,***********,,,,,,,,,
      ,,,,,,,,,,,*********,,,,,,,,,,,
      .,,,,,,,,,,,*******,,,,,,,,,,,,
          ,,,,,,,,,*****,,,,,,,,,.
              ,,,,,,,****,,,,,,
                  .,,,***,,,,
                      ,*,.
    
    
          _______ __  __          __
          / ____(_) /_/ /   ____ _/ /_
      / / __/ / __/ /   / __ `/ __ \
      / /_/ / / /_/ /___/ /_/ / /_/ /
      \____/_/\__/_____/\__,_/_.___/
    
    
      Thank you for installing GitLab!
      GitLab should be available at http://servers IP:8001
    

    Gitlab이 정상적으로 설치되면 Gitlab에서 사용할 포트 및 기타 설정을 해준다.

      # GitLab 설정파일에서 해당 라인 주석 해제 혹은 추가
      $ sudo vi /etc/gitlab/gitlab.rb
      32 external_url 'http://servers IP:8001'
      ...(중략)
      1215 nginx['enable'] = true 
      1216 nginx['client_max_body_size'] = '2G'
    
      # 변경된 설정 적용
      $ sudo gitlab-ctl reconfigure
    
      # 8001 방화벽 해제
      $ sudo firewall-cmd --permanent --add-port=8001/tcp
      $ sudo firewall-cmd --reload
    


    Gitlab의 설정을 끝내고 Gitlab에 접속하기 전에 GCP의 방화벽도 열어주어야 외부에서 접근이 가능하다.

    다음 [그림 3]과 같이 [VPC 네트워크] - [방화벽] 탭에서 servers에서 사용할 포트들을 열어주기 위한 방화벽 규칙을 생성해준다.

    gcp_firewall
    [그림 3] GCP 방화벽 포트 오픈


    GCP 방화벽까지 열어주고나서 servers IP:8001 URL로 접속하면 다음 [그림 3]과 같이 root 계정의 초기 패스워드를 지정하는 화면을 볼 수 있다.

    gitlab_new_password
    [그림 4] GitLab 접속 후 패스워드 변경


2.4. Jenkins 구성

  • Jenkins 설치 및 설정

      # Java 설치
      $ sudo yum install -y java-1.8.0-openjdk-devel.x86_64
    
      # JAVA_HOME 환경변수 확인
      $ echo $JAVA_HOME
    
      $ which javac
      /usr/bin/javac
      $ readlink -f /usr/bin/javac
      /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/bin/javac
    
      # 경로 뒤의 /bin/javac는 제외하고 JAVA_HOME 환경변수 설정
      $ echo "export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64" | sudo tee -a /etc/profile
      $ source /etc/profile
      $ echo $JAVA_HOME
      /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64
    
      # maven 설치 (http://maven.apache.org/download.cgi)
      $ sudo mkdir /tools
      $ cd /tools
      $ sudo yum -y install wget
      $ sudo wget http://mirror.apache-kr.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
      $ sudo tar xzf apache-maven-3.6.3-bin.tar.gz
      $ sudo ln -s apache-maven-3.6.3 maven
      $ sudo vi /etc/profile.d/maven.sh
      export MAVEN_HOME=/tools/maven
      export PATH=${MAVEN_HOME}/bin:${PATH}
    
      $ source /etc/profile.d/maven.sh
    
      $ mvn -version
      Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
      Maven home: /tools/maven
      Java version: 1.8.0_262, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/jre
      Default locale: en_US, platform encoding: UTF-8
      OS name: "linux", version: "3.10.0-1127.el7.x86_64", arch: "amd64", family: "unix"
    
      # Jenkins repo 다운로드 및 key import
      $ sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
      $ sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
    
      # Jenkins 설치
      $ sudo yum install -y jenkins
    
      # Jenkins 기본 포트 변경
      $ sudo vi /etc/sysconfig/jenkins
      56 JENKINS_PORT="8002"
    
      # 8002 방화벽 해제
      $ sudo firewall-cmd --permanent --new-service=jenkins
      $ sudo firewall-cmd --permanent --service=jenkins --set-short="Jenkins Service Ports"
      $ sudo firewall-cmd --permanent --service=jenkins --set-description="Jenkins service firewalld port exceptions"
      $ sudo firewall-cmd --permanent --service=jenkins --add-port=8002/tcp 
      $ sudo firewall-cmd --permanent --add-service=jenkins
      $ sudo firewall-cmd --reload
    
      # 서버 부팅 시 Jenkins 실행
      $ sudo systemctl start jenkins && sudo systemctl enable jenkins
    


    Jenkins에 접속하기 위한 포트는 8002로 설정하였고 GCP 방화벽에서 이미 포트를 열어둔 상태이다.

    jenkins_install
    [그림 5] Jenkins 접속


    servers IP:8002 URL로 접속하면 위의 [그림 5]와 같이 화면이 뜬다.

    화면의 안내에 따라 /var/lib/jenkins/secrets/initialAdminPassword 파일을 확인한다.

      $ sudo cat /var/lib/jenkins/secrets/initialAdminPassword
      f05ef37257944b62a80086bbc4b047bf
    

    Jenkins 초기 비밀번호를 확인하고 입력하여 다음으로 넘어간다.

    다음은 Customize Jenkins라는 페이지가 나오는데, 여기서는 원하는 Jenkins 플러그인을 설치할 수 있다.

    Jenkins 플러그인은 Jenkins가 설치된 이후에도 자유롭게 설치가 가능하기 때문에 Install suggested plugins를 눌러 기본 플러그인만 설치하고 넘어간다.


    Jenkins가 정상적으로 설치되면 다음 [그림 6]과 같은 화면이 뜨게 된다.

    jenkins_main
    [그림 6] Jenkins 정상 설치 화면


이로써 쿠버네티스 클러스터와 GitLab, Jenkins의 설치 및 기본 구성은 끝이 났다.

다음 포스팅에서는 CI(Continuous Integration) 환경을 구성하는 방법에 대해 기술해보겠다!


Tags:
0 comments