Laravel with Jenkins (2/5) – Setup Jenkins on DO


Jenkins 기본 플럭인 설치 화면

이번 포스팅에서는 이전 포스팅에서 설명한 Terraform 으로 젠킨스 설치를 마친 후 Laravel 프로젝트를 위해서 필요한 기본적인 젠킨스 설정 방법을 설명한다. 우선 스크린샷에서 보여지는 것처럼 https://jenkins.example.com 에 접속한 다음, 젠킨스가 추천하는 기본 플럭인들을 모두 설치한다.

기본 플럭인 이외 설치한 플럭인 리스트는 아래와 같다.

  • Slack Notification Plugin : 슬랙 노티피케이션
  • Disk-usage Plugin : 디스크 사용 현황
  • Locale Plugin : 로케일 정보 변경
  • GitHub Branch Source Plugin : GitHub 에서 멀티 브랜치 사용
  • Simple Theme Plugin : 새로운 Material Design theme 을 사용 (현재 적용한 Theme 의 CSS URL 은 아래 링크 참고)
https://cdn.rawgit.com/afonsof/jenkins-material-theme/gh-pages/dist/material-teal.css

테라폼이 DO 인스턴스 생성 후 필요한 프로그램들을 설치/설정하는 스크립트인 jenkins-userdata.sh 10번째 줄을 보면, ssh 키를 생성하는 내용이 있는데, 이 키를 가지고 젠킨스가 GitHub 의 비공개 리포를 access 한다.

Jenkins 인스턴스가 나의 GitHub 비공개 리포로부터 코드를 읽어오려면 이전 포스팅에서 설명했다시피 Jenkins 인스턴스의 SSH public 키를 Github Account > Settings > SSH keys 에 등록해야 한다.

또한 Jenkins 젠킨스의 Credentials > System > Global credentials (unrestricted) 메뉴에서 해당 SSH private 키를 등록한다.

등록할 ssh 키의 위치는 public 키의 경우, /home/jenkins/.ssh/id_rsa.pub 에, private 키는 /home/jenkins/.ssh/id_rsa 에 위치한다.

젠킨스 빌드 트리거 설정화면

GitHub 의 리포에 새로운 브랜치 머지가 이뤄질 때마다 자동으로 젠킨스 파이프라인을 트리거 하도록 만드려면, 위 스크린샷에서 처럼 GitHub hook trigger for GITScm polling 메뉴를 선택 후, GitHub 으로 이동한다.

위 스크린샷에 보이는 것 처럼, GitHub 리포 설정 페이지로 이동하여, Payload URL 을 Jenkins 인스턴스의 웹훅 주소로 지정한다. (참고로 trailing slash 가 생략되지 않도록 https://jenkins.example.com/github-webhook/ 와 같이 지정한다.)

만일 Jenkins Blue Ocean 플럭인 사용을 원한다면, 해당 플러그인을 젠킨스 인스턴스에 설치 후, GitHub 에서 웹훅 주소를 지정하는 대신 Personal Access Token 을 생성한 다음, Jenkins Blue Ocean 플럭인 설정페이지에서 해당 토큰을 입력하면 준비가 완료된다.

다음 포스팅에서는 Laravel PHP 앱 프로젝트에 대한 Continuous Integration 을 위한 준비 과정으로, 테라폼으로 설치한 이 Jenkins 를 사용하여, 해당 프로젝트의 dockerization 준비과정을 설명한다. Docker, Docker Machine, Docker Compose 를 간단히 살펴보고, 어떻게 Laravel PHP 앱 프로젝트를 dockerize 하는지에 대한 설명을 이어가겠다.

Posted by admin in Backend, 0 comments

Laravel with Jenkins (1/5) – Install Jenkins on DO using Terraform

이 글에서는, Laravel 프로젝트 build 파이프라인에 CI/CD 를 사용하기 위하여 Jenkins 를 DigitalOcean 에 설치하고, 더 나아가서는 Docker 를 사용한 전체적인 코드 배포 전략까지 설명한다. 비교적 간단한 Toy Project 의 인프라 설정 작업을 해가면서 정리한 내용이라서 use-case 에 따라서는 더 많은 부분을 고려해야하는 부분이 있겠으나, 다양한 CI/CD best practice 를 고민하는 팀에게 도움이 되었으면 좋겠다. 분량이 적지않아 여러 포스팅으로 나눠 5개 시리즈 포스팅으로 작성했다.

DigitalOcean

DigitalOcean 콘솔 Access Token 생성 페이지

첫번째 해야할 일은 테라폼을 실행하는 로컬 PC 에서 DO 인프라와 인터액션을 하기위한 access 토큰 생성이다. 위 스크린샷에 보이는 것 처럼 DO 콘솔 좌측하단의 API 메뉴를 선택하고 Personal access tokens 의 Generate New Token 버튼을 클릭하면, access 토큰을 생성할 수 있다. 이 토큰은 인프라 리소스를 생성할 수 있어야만 하므로, read/write 권한을 모두 선택한다.

그 다음에 해야할 일은 테라폼으로 생성한 DO 인스턴스와 로컬 PC 간의 ssh 통신을 할 수 있도록 로컬 PC 의 ssh 키를 DO 에 등록하는 과정이다. SysOp 의 로컬 PC 에 이미 ssh 키가 등록되어 있단 가정 하에 아래 명령으로 public ssh 키를 복사한다. (이 ssh 키에는 패스워드가 없어야 하며, 필요하다면 디폴트 ssh 키가 아닌 새로운 ssh 키를 생성하여, 테라폼에서 DO 인스턴스를 생성할 때만 해당키를 지정하여 사용하는 방법도 가능하다.)

cat id_rsa.pub | pbcopy
DigitalOcean 콘솔 SSH key 등록 페이지

위 스크린샷에서 볼 수 있는 것 같이 SSH keys 등록 페이지를 통하여, 로컬 PC 의 SSH 키를 붙여넣고 원하는 이름으로 등록 후 해당키의 fingerprint 를 기록해 둔다. 이는 이후 작업에서, fingerprint 로 지정한 SSH 키를 (또는 복수의 SSH 키들을) 프로비져닝할 인스턴스에 자동으로 등록하기 위함이다.

이제 PC 에 테라폼을 설치한다. 맥 사용자의 경우 homebrew install 명령으로 간단한 설치가 가능하다.

Terraform

https://www.terraform.io/docs/providers/do/index.html

테라폼 문서의 DO 섹션을 보면 테라폼으로 프로비져닝할 수 있는 DO 리소스들이 리스팅 되어 있다. 우리가 우선 사용할 리소스는 digitalocean_droplet 과 digitalocean_record 등 이다.

Provisioning Nginx server using Terraform

이제 로컬 PC 의 적당한 위치에 테라폼 코드를 저장할 폴더를 생성하고 아래와 같은 내용을 갖는 provider.tf 파일을 만든다.

variable "do_token" {}
variable "pub_key" {}
variable "pvt_key" {}
variable "ssh_fingerprint" {}

provider "digitalocean" {
  token = "${var.do_token}"
}

다음으론, 간단한 nginx 서버를 DO 에 프로비져닝하기 위한 nginx.tf 이란 파일을 만들어보자.

resource "digitalocean_droplet" "nginx" {
  image              = "ubuntu-18-04-x64"
  name               = "nginx"
  region             = "sgp1"
  size               = "s-1vcpu-1gb"
  private_networking = true
  monitoring         = true
  ssh_keys = [
    "${var.ssh_fingerprint}",
  ]
  connection {
    user        = "root"
    type        = "ssh"
    private_key = "${file(var.pvt_key)}"
    timeout     = "2m"
  }
  provisioner "remote-exec" {
    inline = [
      "export PATH=$PATH:/usr/bin",
      # install nginx
      "sudo apt-get update",
      "sudo apt-get -y install nginx",
    ]
  }
}

이제까지의 준비로, 테라폼 실행에 필요한 파일들이 모두 준비되었다. 처음 내릴 테라폼 명령은 terraform init 이다. 이 명령은 필요한 플러그인들을 다운받고 초기화한다.

terraform init

초기화 후에는 이번 프로비젼 단계의 인프라 변경내용을 확인하기 위하여, terraform plan 명령을 실행한다. (DO 에서 생성한 access token 값과 ssh fingerprint 값은 각각 DO_TOKEN 과 SSH_FINGERPRINT 환경변수로 미리 export 했다고 가정한다. 또한, 디폴트 id_rsa 가 아닌 별도의 ssh 키를 DO 연결을 위해 생성하였다면 해당 파일 이름으로 변경해야한다.)

terraform plan \
-var "do_token=${DO_TOKEN}" \
-var "pub_key=$HOME/.ssh/id_rsa.pub" \
-var "pvt_key=$HOME/.ssh/id_rsa" \
-var "ssh_fingerprint=$SSH_FINGERPRINT"

Terraform 으로 실제 인프라를 프로비젼하기 위해서는, terraform apply 명령을 실행하면 되며, 약 1분 정도의 시간이 경과된 후에는 새로 생성된 DO droplet 에 nginx 가 설치된 것을 확인 할 수 있다.

terraform apply \
-var "do_token=${DO_TOKEN}" \
-var "pub_key=$HOME/.ssh/id_rsa.pub" \
-var "pvt_key=$HOME/.ssh/id_rsa" \
-var "ssh_fingerprint=$SSH_FINGERPRINT"

생성된 리소스의 정보를 확인하려면 terraform show명령으로 사용한다. 생성된 리소스를 제거하기 위해서는 terraform plan -destroyterraform apply 명령을 순차적으로 실행하던지, 아래와 같이 terraform destroy 명령을 실행하면 된다.

terraform destroy \
  -var "do_token=${DO_TOKEN}" \
  -var "pub_key=$HOME/.ssh/id_rsa.pub" \
  -var "pvt_key=$HOME/.ssh/id_rsa" \
  -var "ssh_fingerprint=$SSH_FINGERPRINT"

만일 nginx 서버 인스턴스 생성시 문제가 발생하였다면, 커맨드라인에서, export TF_LOG=1 이라고 설정한 뒤 나오는 로그 기록을 살펴보면서, trouble-shoot 을 할 수 있다.

테라폼 실행시 환경변수를 인자로 주는 것이 불편하다면, 민감한 credentials 정보를 별도의 파일로 만들고 .gitignore 에 등록하여 그 내용을 보호할 수도 있다. (이 방법의 예는 Dockerized Laravel PHP 앱 인스턴스를 만들때 실제 코드로 살펴볼 예정이다.) 일단, nginx 프로비져닝에 성공 했다면 해당 인스턴스를 제거하고, 이 포스팅에서 목표로 하는 젠킨스 인스턴스를 만들어 보자.

Provisioning Jenkins using Terraform

Jenkins 인스턴스를 만들기 위해, jenkins.tf 라는 파일을 아래와 같이 만든다.

resource "digitalocean_droplet" "jenkins" {
  image              = "ubuntu-18-04-x64"
  name               = "jenkins"
  region             = "sgp1"
  size               = "s-1vcpu-1gb"
  private_networking = true
  monitoring         = true
  user_data          = "${file("config/jenkins-userdata.sh")}"

  ssh_keys = [
    "${var.ssh_fingerprint}",
  ]

  connection {
    user        = "root"
    type        = "ssh"
    private_key = "${file(var.pvt_key)}"
    timeout     = "2m"
  }
}

앞서 보인 nginx.tf 와 비교하면, provisioner “remote-exec” 블럭이 없어지고, user_data 로 jenkins-userdata.sh 라는 이름의 bash 스크립트 파일을 아래와 같이 만들어서 프로그램 설치 및 설정을 실행하도록 했다.

일반적으로는 Terraform/Ansible 조합을 선호하는데, 이후 Dockerized Laravel PHP 앱 인스턴스를 만들때 해당 조합으로 프로비져닝을 할 예정이므로, 비교해 보면 좋겠다.

#!/bin/bash

export DEBIAN_FRONTEND=noninteractive
export PATH=$PATH:/user/bin
export LANG=en_US.UTF-8
export LANGUAGE=en_US:en
export LC_ALL=en_US.UTF-8

# create ssh key to communicate b/w jenkins and repository
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa

# add sudoable jenkins user
adduser jenkins
usermod -aG sudo jenkins
rsync --archive --chown=jenkins:jenkins ~/.ssh /home/jenkins

# add additional apt repositories
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ | sudo tee -a /etc/apt/sources.list.d/jenkins.list'
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository -y "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
add-apt-repository -y ppa:certbot/certbot

# pull the packages for installing jenkins
apt update && apt -y upgrade

# add 2gb swap memory
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
echo 'vm.vfs_cache_pressure=50' | sudo tee -a /etc/sysctl.conf

# setting up a basic firewall
ufw allow 22
ufw allow 80
ufw allow 443
ufw allow 8080
ufw --force enable

# install general tools
apt install -y apt-transport-https ca-certificates
apt install -y software-properties-common python-software-properties build-essential
apt install -y vim git curl htop

# install java
apt install -y openjdk-8-jdk

# install jenkins
apt install -y jenkins

# install docker and docker-compose
apt install -y docker-ce
apt install -y docker-compose

# install nginx
apt install -y nginx

# install certbot
apt install -y certbot python-certbot-nginx

# install s3cmd (for easy dot-env file management)
apt install -y s3cmd

# remove packages no longer needed
apt remove -y --purge software-properties-common
apt -y autoremove

# add jenkins to docker group
sudo usermod -aG docker jenkins

# download nginx config
wget https://gist.githubusercontent.com/jinseokoh/cf21d257fda1d858e298c7322e8b2c5b/raw/60d98dde66133c3cadea53ee4205f740f8438238/nginx.conf -O /etc/nginx/sites-available/default

# let certbot to update nginx config
certbot --nginx --non-interactive --redirect --agree-tos -m "ad[email protected]" -d jenkins.example.com

# restart services
systemctl start jenkins
systemctl reload nginx

# print out initial password
cat /var/lib/jenkins/secrets/initialAdminPassword

위의 스크립트는 테라폼으로 생성한 Jenkins 인스턴스에 필요한 패키지들을 설치한다. 주요 작업 리스트는 아래와 같다.

  • ssh 키 생성
  • jenkins 사용자 생성
  • 2GB swap 메모리 설정 (1G 메모리 밖에 없는 저렴한 인스턴스라…)
  • java, jenkins 설치
  • docker, docker-compose 설치
  • nginx, certbot 설치
  • s3cmd 설치 (Laravel PHP 앱의 dot-env 파일 복사용)
  • nginx reverse proxy 설정

참고로, nginx reverse proxy 설정은 아래의 내용과 같다.

server {
  listen 80 default_server;
  server_name localhost;

  location / {
    proxy_pass            http://localhost:8080;
    proxy_set_header      Host $host:$server_port;
    proxy_set_header      X-Real-IP $remote_addr;
    proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header      X-Forwarded-Proto $scheme;
    proxy_connect_timeout 120;
    proxy_send_timeout    100;
    proxy_buffers         4 32k;
    client_max_body_size  8m;
    client_body_buffer_size 128k;

    # Required for new HTTP-based CLI
    proxy_http_version 1.1;
    proxy_request_buffering off;
    proxy_buffering off; # Required for HTTP-based CLI to work over SSL
    # workaround for https://issues.jenkins-ci.org/browse/JENKINS-45651
    add_header 'X-SSH-Endpoint' 'jenkins.example.com:50022' always;
  }
}

참고로, Jenkins 가 도커 이미지 빌드시 Laravel 코드베이스에서 사용할 .env 파일을 DO Spaces (AWS s3 의 alternative) 에 미리 업로드해 놓고 복사하여 사용할 예정이라서 이 인스턴스에 s3cmd 를 설치했고, s3cmd 의 configuration 은 아래 문서를 참고하여 수동으로 설정했다.

https://www.digitalocean.com/docs/spaces/resources/s3cmd/

Jenkins 인스턴스에서, git 리파지토리로부터 소스코드를 읽어올 수 있도록 GitHub (또는 bitbucket) 의 설정페이지로 이동 후, Jenkins 인스턴스 .ssh 폴더의 id_rsa.pub 키를 등록한다.

해당 인스턴스가 생성된 이후, DO 의 DNS 에 jenkins.example.com 라는 이름의 A 레코드를 등록하는 테라폼 코드를 아래와 같이 추가한다. (참고로, 스크립트 및 설명 전반에 노출되는 도메인 이름은 example.com 로 명명하겠다.)

resource "digitalocean_record" "jenkins" {
  domain = "example.com"
  type   = "A"
  name   = "jenkins"
  value  = "${digitalocean_droplet.jenkins.ipv4_address}"
}

아래 GitHub 리포에 방문하면, 테라폼 관련 코드를 볼 수 있다.

https://github.com/jinseokoh/terraform

이로서, 단 월 $5 의 비용으로 유지/관리할 수 있는 Jenkins 인스턴스를 설치해 보았다. 다음 포스팅에서는 Laravel 프로젝트에서 사용하기 위한 기본적인 Jenkins 설정 내용을 살펴보도록 하겠다.

Posted by admin in Backend, 0 comments

Nuxt and Vuex

vuex 상태 관리 다이어그램
Vuex 앱의 상태를 핸들링하기 위한 일방 통행길을 제공한다. 글로벌 변수의 직접 변경을 방지하기 위한 방법이라고 말하는 사람들이 많은데, 정확한 표현은 아니다. Nuxt 에서 store 폴더 안의 .js 파일에는 대개 일반적으로 위 다이어그램 녹색 점선안의 state, mutations, actions 를 정의해 놓는다. 컴포넌트가 렌더링 될때는, Vuex 의 state 에 접근할 수 있기 때문에 그 컴포넌트가 동작할때 필요한 값을 읽을 수 있고, 렌더링이 된 이후에는 상태값을 변경하기 위한 action 을 dispatch 하여 값을 mutate 할 수 있다.

State

state 는 object 를 리턴하는 간단한 함수다. 일반적으로 포스팅 collection 과 현재 선택한 포스팅 객체 등을 저장하는 장소가 되며, application level 의 single source of true 로 사용한다. 이때 주의할 점은 VueJs 의 reactivity 를 위해서, deeply nested 객체를 가급적 지양해야한다는 것이다. 그렇지 않는다면 shallow copy 한 object 를 다시 assign 해야하는 일이 생긴다. 따라서, thing 에 대해 다양한 mutation 이 있어야만 한다면, state.things.thing 패턴보다는 state.thing 패턴을 사용하는 것이 좋다.

Getters

getter 는 store 를 위한 computed 프로퍼티라고 생각하면 된다. getter 는 computed 프로퍼티처럼 대부분 커스텀 셋터를 사용하지 않고 읽기전용 값들의 조합으로 사용되나 셋터를 사용하는 것도 가능하다. vuex 에는 mapGetters 헬퍼가 있어서, Nuxt 에서 name spaced 스토어를 필요한 getter 들을 computed 프로터티로 불러올 수 있다. 명심할 점은 getter 는 읽기 전용이다. 변경을 하려면, action 을 dispatch 해야 한다.

Mutations

mutation 은 synchronous 하게 store 의 상태값을 변경하는 함수 들이다. mutation 은 상태 변경을 commit 하는 것이고 각각의 commit 은 synchronous 한 동작이다. 이에 반하여, 아래에 설명할 actions 은 일련의 asynchronous 한 작업을 수행 후, mutation commit 을 호출한다. mutation 은 reactive 이벤트 이므로, 이를 직접 commit 하면 안된다. mapGetters 헬퍼와 비슷한 mapMutations 헬퍼가 있다.

Actions

action 은 백앤드 API 를 호출하여 결과값을 불러오는 것과 같은 asynchronous 한 상태변경을 위하여 사용한다.
Posted by admin in Backend, 0 comments

Laradock 에서 cron 스케쥴러 및 Laravel Horizon 설치

Cron scheduler 와 queue 를 사용하는 로직을 로컬에서 구현하기 위해서는 local 피씨에도 해당 인프라를 만들어 놓는 것이 필요한데, Laradock 의 기본 설정은 내가 원하는 설정 부분이 빠져있거나, 사소한 오류로 인하여, Out of box 동작이 되지 않았기 때문에, 이를 위한 로컬 셋팅 작업을 한 뒤, 이 글을 빌어 그 기록을 남겨둔다.

cron 스케쥴러

크론 스케쥴러는 workspace 컨테이너에서 동작하며, 아래와 같이 workspace/crontab/laradock 에 내용을 아래 샘플과 같이 수정한 뒤 컨테이너 빌드를 다시하면 동작했다. * * * * * laradock php /var/www/backend/artisan schedule:run >> /dev/null 2>&1 컨테이너 리빌드는 아래 명령을 사용하면 된다. docker-compose up -d --force-recreate --build workspace

Laravel Horizon

Laravel 다양한 queue 옵션들 중에, 이번 설정에서는 Redis 큐를 사용했다.
  1. Laravel Horizon 설치
공식 문서의 설명대로 아래 일련의 명령들을 사용하면 패키지 설정과 DB 설정이 마무리된다.
composer require laravel/horizon
php artisan horizon:install
php artisan queue:failed-table
php artisan migrate
  1. Laravel codebase 내의 설정
Redis connection 등의 구체적인 설정내용을 명시하는 아래의 설정 파일들을 수정해야 하는데, 그 내용은 공식 문서내에 설명이 상세하게 되어 있으므로 구체적 내용은 생략한다.
config/horizon.php
config/database.php
config/queue.php
.env
  1. Laradock 설정
php-worker 컨테이너에 관련한 디렉토리내의 모든 파일들을 수정했었어야만 했는데, 어느 부분을 어떻게 수정했는지를 일일이 설명하는 것이 오히려 한눈에 변경사항을 알아보기가 어려울 듯 해서, 현재 로컬 피씨 해당 디렉토리내의 working example 을 그대로 리스팅하였다. 3.1. Dockerfile
#
#--------------------------------------------------------------------------
# Image Setup
#--------------------------------------------------------------------------
#

ARG PHP_VERSION=${PHP_VERSION}
FROM php:${PHP_VERSION}-alpine

LABEL maintainer="Mahmoud Zalt <[email protected]>"

RUN apk --update add wget \
  curl \
  git \
  build-base \
  libmemcached-dev \
  libmcrypt-dev \
  libxml2-dev \
  zlib-dev \
  autoconf \
  cyrus-sasl-dev \
  libgsasl-dev \
  supervisor

RUN docker-php-ext-install mysqli mbstring pdo pdo_mysql tokenizer xml pcntl
RUN pecl channel-update pecl.php.net && pecl install memcached mcrypt-1.0.1 && docker-php-ext-enable memcached

# Install PostgreSQL drivers:
ARG INSTALL_PGSQL=false
RUN if [ ${INSTALL_PGSQL} = true ]; then \
    apk --update add postgresql-dev \
        && docker-php-ext-install pdo_pgsql \
;fi

# Install Redis
ARG INSTALL_PHPREDIS=false

RUN if [ ${INSTALL_PHPREDIS} = true ]; then \
    # Install Php Redis Extension
    printf "\n" | pecl install -o -f redis \
    &&  rm -rf /tmp/pear \
    &&  docker-php-ext-enable redis \
;fi

RUN rm /var/cache/apk/* \
    && mkdir -p /var/www

#
#--------------------------------------------------------------------------
# Optional Supervisord Configuration
#--------------------------------------------------------------------------
#
# Modify the ./supervisor.conf file to match your App's requirements.
# Make sure you rebuild your container with every change.
#

COPY supervisord.conf /etc/supervisord.conf

ENTRYPOINT ["/usr/bin/supervisord", "-n", "-c",  "/etc/supervisord.conf"]

#
#--------------------------------------------------------------------------
# Optional Software's Installation
#--------------------------------------------------------------------------
#
# If you need to modify this image, feel free to do it right here.
#
    # -- Your awesome modifications go here -- #

#
#--------------------------------------------------------------------------
# Check PHP version
#--------------------------------------------------------------------------
#

RUN php -v | head -n 1 | grep -q "PHP ${PHP_VERSION}."

#
#--------------------------------------------------------------------------
# Final Touch
#--------------------------------------------------------------------------
#

WORKDIR /etc/supervisor/conf.d
3.2. supervisord.conf
[supervisord]
nodaemon=true
[supervisorctl]
[inet_http_server]
port = 127.0.0.1:9001
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[include]
files = /etc/supervisor/conf.d/*.conf
3.3. supervisord.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/backend/artisan queue:work redis --sleep=3 --tries=3
autostart=true
autorestart=true
numprocs=4
redirect_stderr=true
3.4. supervisord.d/horizon.conf
[program:horizon]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/backend/artisan horizon
autostart=true
autorestart=true
;user=laradock
numprocs=1
redirect_stderr=true
;stdout_logfile=/etc/supervisor/conf.d/horizon.log
  1. docker-compose.yml
마지막으로 상위 폴더에 있는 docker-compose.yml 파일도 PHP Worker 관련 설정이 아래와 같이 수정되었다.
### PHP Worker ############################################
    php-worker:
      build:
        context: ./php-worker
        args:
          - PHP_VERSION=${PHP_VERSION}
          - INSTALL_PHPREDIS=${PHP_WORKER_INSTALL_PHPREDIS}
          - INSTALL_PGSQL=${PHP_WORKER_INSTALL_PGSQL}
      volumes:
        - ${APP_CODE_PATH_HOST}:${APP_CODE_PATH_CONTAINER}
        - ./php-worker/supervisord.d:/etc/supervisor/conf.d
      depends_on:
        - workspace
      extra_hosts:
        - "dockerhost:${DOCKER_HOST_IP}"
        - "demo.test:${HOST_IP_ADDRESS}"
        - "backend.test:${HOST_IP_ADDRESS}"
      networks:
        - backend
위 Cron 스케쥴러때와 마찬가지로 아래와 같은 컨테이너 리빌딩이 필요하다. docker-compose up -d --force-recreate --build php-worker 리빌딩이 끝나면 정상적으로 동작 하겠지만, 혹시나 supervisor 설정을 변경했을 경우, 아래의 명령으로 커맨드라인 접속 후 docker-compose exec php-worker ash supervisor 의 새로운 설정내용을 반영하도록 한다.
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
이제 backend.test/horizon 에 접속하면, 위의 dashboard 가 출력되는 것을 확인할 수 있다. Yay!

Further readings

https://laravel.com/docs/5.7/queues https://laravel.com/docs/5.7/horizon
Laravel Queues & Horizon Dashboard
Posted by admin in Backend, 0 comments

Setting up Laravel project on DigitalOcean

목표

이 글은 D/O 의 Droplet 과 LaraSail 과 Deployer 를 이용하여, 몇가지 추가 설정을 해주면, Laravel 프로젝트를 배포할 수 있는 초간편 LEMP 환경을 구축할 수 있다는 내용을 소개한다.

DigitalOcean

Ubuntu 18.04 D/O Droplet 생성후, IP 주소를 가지고 D/O DNS 에 그 주소를 연결할 도메인을 등록한다. (편의상 example.com 으로 지칭하겠다.) 이전엔, Project 메뉴가 없었는데, Project 별로 관리할 수 있는 메뉴가 생겨서 같은 콘솔 메뉴아래에서 관리할 수 있어서 편하군. 이제 ssh 로 터미널 접속한다.
ssh [email protected]

LaraSail

Go to LaraSail and read the f… document there before you run the command below which is copied from the document. It all starts with;
curl -sL https://github.com/thedevdojo/larasail/archive/master.tar.gz | tar xz && source larasail-master/install
설치가 끝나면 이제 다음 커맨드를 실행한다.
larasail setup
LaraSail 을 이용하면 LEMP stack 을 한방에 (composer, php7.2, mysql, nginx) 설치할 수 있다. 몇분간의 설치가 진행된 다음 LaraSail 로고와 함께 종료 메시지가 보였다면, post_larasail.sh 라는 파일을 아래의 내용으로 만들고 실행하자. (Don’t forget to give it a 755 permission.)
위 스크립트는 기본 사용자인 larasail 을 SSH 접속 가능하게 만들며, 다음 단계에서 코드 배포를 위해 사용할 deployer 를 추가하고, Timezone, Locale 설정 등과 같은 house keeping chores 를 수행한다. (It definitely saved me some time. So you can expect the same as well.)

Deployer

Now, it’s time to take a look at Deployer unless you’ve been playing with it. Deployer 프로젝트는 git 을 이용하여 Laravel project 를 (Laravel Forge 를 비롯한 많은 deployment 서비스의 사용하지 않아도 쉽게 배포할 수 있도록 하는 alternative) 아직 젠킨스 같은 CI 셋업이 제대로 되지 않은 초기 toy project 단계에서 쉽고 빠르게 사용할 수 있는 유용한 뭐 그런. 대충 감 잡길.
  1. 로컬PC 에 dep 설치
curl -LO https://deployer.org/deployer.phar
mv deployer.phar /usr/local/bin/dep
chmod +x /usr/local/bin/dep
  1. 로컬PC 에 deployer 사용자 전용 RSA 키 생성
ssh-keygen -t rsa -b 4096 -f  ~/.ssh/github_rsa
또는
ssh-keygen -t rsa -b 4096 -f  ~/.ssh/bitbucket_rsa
와 같이 생성한다. 결국, 이 키를 사용하여 로컬PC <-> D/O 서버의 deployer 사용자 접속하는 것이다.
  1. Git 서버 등록
다음에는, D/O 서버의 deployer 사용자와 <-> GitHub 간의 접속을 위해서, /home/deployer/.ssh/id_rsa.pub RSA 키와 이전 2번단계에서 로컬PC 내에 생성한 RSA 키를 GitHub 에 등록한다. 등록법은 아래와 같고 두개를 모두 등록해주면 된다. GitHub 계정에 SSH키 등록에 관한 설명 이전 2번단계에서 로컬PC 에서 생성한 RSA 키는 아래의 명령으로 서버 deployer 사용자 .ssh 폴더의 authorized_keys 에도 추가해야만 ssh 접속이 가능해진다.
ssh-copy-id -i -f ~/.ssh/github_rsa.pub [email protected]
이제까지의 일련의 과정을 걸친 상태에서는 아래와 같은 명령으로 로컬PC 에서 D/O 서버의 deployer 사용자로 SSH 접속이 가능해야 한다.
ssh [email protected]  -i ~/.ssh/github_rsa
또한, 서버의 deployer 사용자로 접속시, 아래 명령으로 GitHub 과의 연결도 가능함을 확인 할 수 있어야 한다.
ssh -T [email protected]
정상적일때 아래와 비슷한 메시지를 볼 수 있을 것이다; Hi jinseokoh! You’ve successfully authenticated, but GitHub does not provide shell access.

Nginx

아래의 코드로 nginx sites-available 설정을 하고
sudo nano /etc/nginx/sites-available/example.com
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
certbot 을 이용하여, 아래의 명령으로 nginx 설정을 https 로 갱신한다.
sudo certbot --nginx -d example.com -d www.example.com
갱신 테스트를 해보려면 아래의 명령을 실행하여 결과를 확인한다.
sudo certbot renew --dry-run

MySQL

MySQL 은 LaraSail 스크립트가 이미 설치를 한 상태이므로, MySQL 의 root password 를 알기위해서는 아래 명령을 실행한다.
larasail mysqlpass
추가적으로 command line 혹은 Sequel Pro 와 같은 client 를 이용하여 아래의 계정을 생성한다.
CREATE DATABASE science DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'my-user'@'localhost' IDENTIFIED BY 'my-password';
GRANT ALL ON science.* TO 'my-user'@'localhost';
FLUSH PRIVILEGES;

Deployer

Local

작업 폴더로 이동후 아래의 명령을 입력하면
cd /Code/laravel-app
dep init -t Laravel
아래와 같은 메시지가 출력됨을 볼 수 있다. Successfully created: /Users/chuck/Code/blog/deploy.php 이제 IDE 를 열어서 위 파일을 수정한다. 1) Project name 2) Project repository
github 또는 bitbucket repo 주소
3) Host 정보
host('159.65.xxx.xxx')
    ->user('deployer')
    ->identityFile('~/.ssh/id_rsa')
    ->set('deploy_path', '/var/www/html/laravel');
4) 맨마지막 라인 주석처리
// before('deploy:symlink', 'artisan:migrate');
deploy 시에는 dep deploy 명령만 치면 된다. 단, git deploy 할 폴더가 deployer 권한으로 변경해야만 한다.
sudo chown -R deployer:www-data /var/www/html
첫번째 deploy 시 행해야 하는 몇가지 일상적인 작업 .env 설정 php artisan migrate php artisan key:generate php artisan config:cache 등의 작업을 수행후 https://example.com 으로 접속하여, 정상적으로 동작하는 지를 확인해본다. 이로서, Deployer 를 이용한 laravel 프로젝트 deploy 를 쉽게 하는 방법을 알아 보았데헷. 레퍼런스)
  • https://www.digitalocean.com/community/tutorials/how-to-install-linux-nginx-mysql-php-lemp-stack-ubuntu-18-04
  • https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-18-04
  • https://www.digitalocean.com/community/tutorials/automatically-deploy-laravel-applications-deployer-ubuntu
Posted by admin in Backend, 0 comments

Vue SPA from scratch using Laravel 5.7 backend

Yes, you were told it’ll be built from scratch. Here’s the thing. I will list step by step instructions to explain some of important updates whenever I make progress with this little toy project. So, feel free to check out this page later as this gets updated on a regular basis. hopely… though. Let’s start off with backend using the latest Laravel, which is 5.7.

Laravel installation

just for the record, I am using Laradock for the local development, which is not in the scope of this article.
composer create-project --prefer-dist laravel/laravel toy
then copy .env from .env.example and edit it to reflect your local development environment. then update composer.json to include;
"barryvdh/laravel-cors": "^0.11.2",
"laravel/socialite": "^3.0",
"spatie/laravel-query-builder": "^1.11",
"tymon/jwt-auth": "^1.0.0-rc.3"
then, go through the each of the followings one by one.
php artisan key:generate
php artisan jwt:secret
php artisan make:auth
php artisan migrate
php artisan vendor:publish --provider="Barryvdh\Cors\ServiceProvider"
php artisan vendor:publish --provider="Spatie\QueryBuilder\QueryBuilderServiceProvider" --tag="config"
update app/Http/Kernel.php to include;
protected $middleware = [
    // ...
    \Barryvdh\Cors\HandleCors::class,
];
Okay, so far the very basic of Laravel setup is completed. Thanks to Laravel, you can safely think the most of web development settings are in place and ready to go. But, as we are aiming at building a SPA. Let’s go further with tymon/jwt-auth package configuration for your API auth.

JWT configuration

update config/auth.php to change api guard
    'defaults' => [
            'guard' => 'api',
                // ...
        ],
        :
    'guards' => [
            // ...
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],

and add the following api routes; /routes/api.php
Route::group(['middleware' => 'auth:api'], function () {
    Route::post('logout', 'Auth\[email protected]');

    Route::get('/user', function (Request $request) {
        return $request->user();
    });
});

Route::group(['middleware' => 'guest:api'], function () {
    Route::post('login', 'Auth\[email protected]');
    Route::post('register', 'Auth\[email protected]');
    Route::post('password/email', 'Auth\[email protected]');
    Route::post('password/reset', 'Auth\[email protected]');
});
then, update User model to implement JWTSubject interface like so.
use Tymon\JWTAuth\Contracts\JWTSubject;
:

class User extends Authenticatable implements JWTSubject
:
    /**
     * @return int
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}
then, update the following 4 controllers for jwt guard;
  • app/Http/Controllers/ForgotPasswordController.php
  • app/Http/Controllers/LoginController.php
  • app/Http/Controllers/RegisterController.php
  • app/Http/Controllers/ResetPasswordController.php
then remove app/Http/Controllers/HomeController.php and app/Http/Controllers/Auth/VerificationController.php. and finally, update RedirectIfAuthenticated.php as follows;
if (Auth::guard($guard)->check()) {
    return response()->json(['error' => 'Already authenticated.'], 400);
}
Now that you will be able to create a valid JWT token with your login credentials. Let’s try out with Postman. As you can see in the picture below, I could get a valid token. Sweet.

Further back-end considerations

We can remove web routes entirely from this project or define a catch-all route as follows. It’s just upto you.
Route::get('{any}', function () {
    return view('index');
})->where('any', '(.*)');
With this basic back-end scaffolding, we can now move on to the front-end part.

Fron-end setup

As you’ve already noticed, this project will exploit some of the best-known VueJS projects including; Let’s start off with vue-cli first;
npm install -g @vue/cli
Posted by admin in Backend, 0 comments