Laravel 5.5로 업그레이드
Laravel 5.4 에서 5.5 로 업그레이드 후기
업그레이드
공홈을 참조해도 된다.
composer.json에서 아래 패키지의 버전을 바꿔준다.
dependencies
- laravel/framework: 5.5.*
- phpunit/phpunit: ~6.0
dev-dependencies
- filp/whoops: ~2.0
composer clearcache
composer update
이슈
Session, DB 문제
php artisan cache:clear
const UPDATE_AT 문제
const UPDATED_AT = null;처럼 timestamps 필드 중 하나를 disable 했을 때 5.5 버전에선 오류가 발생한다.
아래 처럼 모델에 setUpdatedAt 함수를 추가해주면 된다.
<?php
public function setUpdatedAt($value) {
return $this;
}
request has 문제
request->has와 같은 기능으로 동작하려면 request->filled로 바꿔줘야한다.
<?php
// 5.5에서는 name 값이 비던 안 비던 true
if ($request->has('name')) {
}
// 이게 구버전 has의 기능과 동일하다.
// name 값이 있을 경우만 true
if ($request->filled('name')) {
}
Laravel - Socialite InvalidStateException
가끔 가다가 인증이 안 되는 경우가 있다.
해결법
stateless
Socialite::driver('인증 타입')->stateless()->user(); 로 가져오자.
session 명 변경
config/session.php의 cookie 값을 변경해준다.
session domain 변경
config/session.php의 domain 값을 null 에서 내 도메인으로 변경한다.
그리고 아래 두 명령어를 실행해주자.
php artisan cache:clear
composer dump-autoload
여담
socialite 설명에선 나오지 않았지만, Socialite 구문을 try/catch로 감싸주는게 좋았다.
<?php
try {
$user = Socialite::driver('facebook')->stateless()->user();
} catch (\Exception $e) {
return redirect()->route('login');
}
Laravel Query Logging, 쿼리 로그
Laravel DebugBar 를 이용하는게 편하지만 dump 나 json 리턴시에 DebugBar 가 보이지 않으므로 직접 찍어줘야하는 경우가 많다.
소스
<?php
## DB 파사드를 추가한다.
use DB;
...
public function your_func(Request $request) {
// 로그를 enable 시키고
DB::enableQueryLog();
// 쿼리를 여기에 실행한다.
Member::where('조건', '값')->get();
Product::find(1);
// 쿼리 로그를 찍는다.
$queryLogs = DB::getQueryLog();
dump($queryLogs);
}
결과
배열에 query, bindings (preparedStatement 를 위한 것), time 이 상세하게 나온다.
Laravel 5.5 - Debugbar와 BrowserSync의 충돌 해결
Laravel Mix로 browserSync 옵션을 활성화 시에 Debugbar가 생기지 않는 오류가 발생할 경우 (스크립트 단에서 JSON parse 오류가 발생한다) 다음과 같이 설정해주면 된다.
해결
mix.browserSync({
proxy: {
// artisan serve시의 주소
target: "localhost:8000",
reqHeaders: function () {
// host를 직접 지정해준다.
return {
host: "localhost:3000",
};
},
},
});
Cannot start container iptables failed
오류
Container를 다시 올릴 때 다음과 같은 오류로 올라가지 않는 경우가 있다. docker kill이 아닌 stop, rm으로 container를 지웠을 때 뭔가 충돌이 나는 것 같다.
Error response from daemon: Cannot start container aca936f2822fce32235e627ff539c58b74b2f4e66cfa701de47ce609e2590d13: iptables failed: iptables -t nat -A DOCKER -p tcp -d 0/0 --dport 50000 -j DNAT --to-destination 172.17.0.10:50000 ! -i docker0: iptables: No chain/target/match by that name.
(exit status 1)
해결
이 중 마음에 드는 방법으로 해결하면 된다.
서비스 restart
docker 서비스를 restart 한다.
iptables rule 추가
오류 메세지에 필요한 rule을 추가한다.
iptables -t filter -N DOCKER
iptables -t nat -N DOCKER
AWS CodeCommit 사용하기
AWS 에서 제공하는 Cloud Git Repository 인 CodeCommit 으로 소스코드를 관리해보자.
설정
공식 문서가 아주 잘 되어있다. 문서를 보고 시작해도 된다.
IAM 유저 생성
IAM 에서 유저를 만든 뒤에 AWSCodeCommitFullAccess 권한을 추가한다.
키 파일 업로드
ssh 키를 생성하고 Public Key 파일(id_rsa.pub)을 해당 유저의 Security credentials 메뉴에서 업로드한다. 키가 등록되면 SSH key ID가 보이는데 메모해 놓자.
config
사용할 유저의 ssh config 를 설정해줘야한다. User 에 들어가는 값은 위에서 적어놓은 SSH key ID 값이다.
$ vi ~/.ssh/config
## 아래 내용을 맨 위에 넣어주자.
Host git-codecommit.*.amazonaws.com
User EXAMPLEEXAMPLEEXAMPLE
IdentityFile ~/.ssh/id_rsa
## 저장한 뒤 권한을 바꿔준다.
chmod 600 ~/.ssh/config
테스트
설정이 완료된 후에 서울리젼으로 ssh 연결을 시도해보자.
$ ssh git-codecommit.ap-northeast-2.amazonaws.com
## 다음과 같은 메세지가 리턴되면 성공이다.
You have successfully authenticated over SSH. You can use Git to interact with AWS CodeCommit. Interactive shells are not supported.Connection to git-codecommit.
ap-northeast-2.amazonaws.com closed by remote host.
Connection to git-codecommit.ap-northeast-2.amazonaws.com closed.
연동
이제 AWS CodeCommit 과 내 소스를 연결시켜보자.
## 이미 존재하는 프로젝트의 경우
$ git remote remove origin
$ git remote add origin ssh://git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/레파지토리명
## 처음 시작하는 경우
$ git clone ssh://git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/레파지토리명
## 권한이 없거나 ssh 계정을 물어볼 때
## origin의 path 앞에 SSH key ID를 추가하자
$ git remote add origin ssh://EXAMPLEEXAMPLEEXAMPLE@git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/레파지토리명
git pull origin master시에 정상적으로 가져오는 걸 확인할 수 있다.
ssh 연동이 잘 되지 않을시
메뉴얼대로 따라해도 연동이 안될 때는 당황하지 말고 IAM > Users > Security credentials에서 HTTPS Git credentials for AWS CodeCommit를 만들어주고 CodeCommit Repository 연결을 http로 하면 된다.
Webhook
Github, Bitbucket 와 달리 Webhook 설정하는 법이 조금은 복잡하다. (GCP 의 Pub/Sub 와 비슷한 느낌)
CodeDeploy 서비스를 사용하면 쉬워질 것 같은데, 그럼 CodePipeline 도 쓰고 싶을 것 같고 CI 세팅을 해야되고 다음 기회에
Lambda
람다는 웹(URL)으로 호출할 수 있는 Javascript function 이다.
Lambda 설정
먼저 Lambda > 함수 > 함수생성 > 새로 작성 메뉴에서 webhook 이란 이름의 함수를 생성한다. 트리거 구성 메뉴에서 CodeCommit 을 선택하고 입력 폼을 잘 채워주자.

기존 브랜치로 푸시, master 브랜치를 선택했다.
사용자 지정데이터에는 webhook 을 걸 URL 경로를 넣어준다. (예: https://yourdomain.com/webhook)
소스
이미 세팅된 함수를 사용하자.
"use strict";
const url = require("url");
const https = require("https");
exports.handler = (event, context, callback) => {
const webhook_url = event.Records[0].customData;
if (!webhook_url) {
const error = new Error("Web-hook URL not provided as custom data.");
callback(error);
} else {
console.log("POST web-hook to " + webhook_url);
const options = url.parse(webhook_url);
options["method"] = "POST";
const req = https.request(options, (res) => {
let body = "";
console.log("Status:", res.statusCode);
console.log("Headers:", JSON.stringify(res.headers));
res.setEncoding("utf8");
res.on("data", (chunk) => (body += chunk));
res.on("end", () => {
console.log("Successfully triggered web-hook.");
// If we know it's JSON, parse it.
if (res.headers["content-type"] === "application/json") {
body = JSON.parse(body);
}
callback(null, body);
});
});
req.on("error", callback);
req.end();
}
};
https 커넥션이 아닌 경우 http 모듈을 사용해서 request 를 보내면 될 것 같다.
등록 후엔 CodeCommit > 트리거 > webhook > 트리거 테스트를 진행하면 정상적으로 호출이 된다.
로그
호출 로그는 CloudWatch > 로그에서 생성한 Lambda 명으로 확인할 수 있다.
로그 스트림이 설정되지 않았을 경우엔 Lambda > 구성 > 기존 역할에 표시된 역할(Rule)이 해당 Lambda 함수를 잡고 있는지 확인해야한다. IAM > Rules > 해당 룰 > Permissions에서 Show policy를 누르면 Resource 속성에서 확인할 수 있고 다르다면 제대로 연결시켜주면 된다.
여담
- AWS Korea week in review에 소개되었다.
Laravel 5.5 - Log Permission 문제
웹 서버의 유저로 로그 파일이 생성되어야 하는데, 어느 순간부터 root:root 권한을 달고 daliy log 가 생성되는 경우가 있다.
여러가지 경우의 수가 있는데, 맞는 조건을 찾아서 Permission 오류가 발생하지 않게 처리해보자.
selinux
storage 에는 쓰기권한이 있어야한다.
chcon -R -t httpd_sys_rw_content_t storage
WebServer user
웹 서버의 유저가 다르게 설정 되어있을 때 권한이 바뀔 수 있다. 서버 설정을 열어서 유저가 제대로 설정되어 있는지 확인해보자.
user nginx;
php-fpm user
php-fpm 에서 설정하는 user 와 group 이 다르게 설정 되어 있을 때 권한이 바뀔 수 있다. php-fpm 설정을 열어 유저가 제대로 설정되어 있는지 확인해보자.
user = nginx
group = nginx
listen.owner = nginx
listen.group = nginx
log rotate
로그 파일이 너무 커지는 걸 막기위해 log rotate 설정이 되어있다면 권한이 바뀔 수 있다. logrotate 가 cron 에 물려 있는지 설정을 확인해보자.
$ pwd
/etc/logroate.d
$ vi nginx
$ vi php-fpm
cron 사용시
crontab 의 경우 root 유저로 실행이 되면 cron 에서 Laravel 을 호출할 때 log 가 root 권한으로 생성될 수 있다.
로그 분기
log 파일을 생성하는 프로세스별로 분기해서 해결할 수 있다. Stackoverflow 참조
$app->configureMonologUsing(function(Monolog\Logger $monolog) {
$filename = storage_path('logs/laravel-'.php_sapi_name().'.log');
$handler = new Monolog\Handler\RotatingFileHandler($filename);
$monolog->pushHandler($handler);
});
설정을 추가해 놓으면 logs 폴더 하위에 다음과 같이 로그가 분기되어 생성된다.
$ ls -al .
-rw-r--r-- 1 nginx nginx 718 Aug 18 10:56 laravel-fpm-fcgi-2017-08-18.log
cron 은 root 에서 실행되나 user shell 에서 Laravel 프로세스를 실행하는 경우 root 에 의해 log 가 생성되었다면 다음과 같이 permission 을 변경해서 생성해야한다. Post 참조
$app->configureMonologUsing(function(Monolog\Logger $monolog) {
$filename = storage_path('/logs/laravel-' . php_sapi_name() . '.log');
// 5번째 파라미터로 666 권한을 넘긴다.
$handler = new Monolog\Handler\RotatingFileHandler($filename, 0, \Monolog\Logger::DEBUG, true, 0666);
$monolog->pushHandler($handler);
});
RotatingFileHandler 의 Parameter 는 여기를 참조하자.
666 으로 생성 시엔 굳이 php_sapi_name()을 사용하지 않아도 된다. (rw 권한이 모두에게 있으니까)
setfacl
다른 해결 방법으로는 ACL 을 수정해 logs 폴더 자체를 해당 user:group 이 편집할 수 있게 처리하면 된다.
$ pwd
/public_html/storage/logs
$ setfacl -d -m u:nginx:rwx .
$ getfacl .
default:user:nginx:rwx
## 삭제
$ setfacl -d -x u:nginx .
여담
Laravel 프로젝트 시작시 bootstrap/app.php 안에 로그를 분기 로직을 넣고 개발하는 게 좋아보인다.
Docker로 LEMP Stack 구축하기
이 포스트 전에 웹서버 세팅을 하나씩 설치해서 띄워보는 걸 권장하고 Docker, SSH Login, LetsEncrypt, sed 명령어의 사용법을 알고 있어야 한다. 구성할 서버 스택은 다음과 같다.
- Docker
- Docker-compose
- Host 에 사용될 Linux (Centos7)
- Alpain Linux
- Nginx ^1.13
- MariaDB ^10.2
- PHP ^7.1
- Laravel =5.4
- LetsEncrypt
- HTTP2
- Redis
Docker 설치
이전 포스트를 참조하자.
Container 쇼핑
Docker Hub에서 마음에 드는 Container 를 사용해도 되지만, 생각처럼 돌아가는 Container 는 다음과 같았다.
- nginx-php-fpm (2M)
- official mariadb (10M)
- bitnami redis (500K)
왜 Laradock을 안 썼죠?
- Laradock 에서 caddy 를 사용하지 않고 nginx 와 certbot 만을 이용해 http2 환경을 구성하는 예제가 없었다.
- 그래도 시도해봤으나 certbot 인증시에 DocumentRoot 를 잡지 못하는 현상을 삽질로 매꿀 시간이 없었다.
- Git repo 를 Clone 받아서 Docker-compose 로 Container 를 구동하기 때문에 추후 ECS 에 적용할 수가 없는 구조였다.
- 직접 구축해보고 싶었다.
세팅
nginx-php-fpm
Laravel 용 및 튜닝을 위해 Docker hub 의 이미지 대신 Git repo 의 이미지를 Clone 해서 세팅을 해보자.
내용 추가 중..
AWS ECS 부수기
ECS 는 서울 리젼이 아직 없어서 그런가, 구글링해도 사용할만한 데이터가 너무 적었다. (기초 설명은 잘 되있다. 하지만 Hello World Application 을 올리려고 ECS 를 쓰는 건 아니니까..)
용어
Cluster
Amazon ECS 클러스터는 작업을 배치할 수 있는 컨테이너 인스턴스의 논리적 그룹화입니다. 이 공식 설명을 보고 Cluster 에 대한 감을 잡기가 쉽지 않았다.
- 간단히하면 Docker Container 를 올리는 EC2 Instance 이다.
- K8S 의 그 클러스터이다.
Task
- Task 는 작업이라고 번역되며, 하나의 Task Definition JSON 은 하나의 Docker-compose YAML 이라고 보자.
Container Definition
- 배포되는 각 컨테이너의 정의
- 파드의 개념
Task Definition
- 컨테이너의 집합인 Task의 정의
- 파드의 개념
Service
Amazon ECS 는 단일 ECS 클러스터에서 작업 정의에 지정된 수("원하는 개수")의 인스턴스를 동시에 실행 및 관리할 수 있게 해줍니다. 어떤 이유로 작업이 실패 또는 중지되는 경우 Amazon ECS 서비스 스케줄러가 작업 정의의 다른 인스턴스를 시작하여 이를 대체하고 서비스의 원하는 작업 수를 유지합니다.
- Task 를 자동으로 관리할 수 있게 하는 기능, LB 나 Auto Scaling 모두 여기서 적용이 된다.
- 롤링 업데이트 시에 배포 및 오토 스케일링을 담당한다.
- K8S의 서비스, 디플로이먼트, 레플리카세트의 역할
Repository
AWS Docker 레지스트리 서비스로 AWS Private DockerHub 라고 보자.
네트워크 모드
| 모드 | 내용 |
|---|---|
| default | 네트워크 모드 기본값으로 bridge와 같음 |
| awsvpc | AWS에서만 제공되는 네트워크 모드 (ENI가 Task 자체 VPC의 IP 주소할당) |
| bridge | DockerContainer를 호스트와 같은 네트워크에 배치해 라우팅 없이 컨테이너 접근 가능 |
| host | Task가 배치되는 호스트의 네트워크를 공유하는 모드 (Fargate 사용 불가) |
| none | Task에 속한 컨테이너의 외부 접근이 불가능하고 포트 매핑 사용불가 |
볼륨 생성
-v 또는 --volume 으로 Host 의 폴더를 Mount 하는 기능이 꼭 필요한데, 설정 창에선 찾기가 너무 힘들었다. ECS 에서는 작업 정의 생성 시에 하단에 볼륨 추가 를 꼭 먼저 클릭해 볼륨부터 추가해야한다.
이름엔 --name 옵션 사용하듯이 닉네임을 넣고
소스 경로엔 Host directory 경로를 넣자.
추가가 되면 컨테이너 추가 시에 탑재 지점 메뉴의 소스 볼륨 select box 에서 선택할 수 있다.

이 짓을 하는 것보단 공식 문서의 Task Definition JSON Parameter 를 보고 JSON 으로 때려박는게 편하다.
예시 JSON 은 아래와 같다. (3306 과 3307 을 열고 Host 의 Data 폴더를 Mount 하는 기본 구성의 MariaDB Image)
{
"requiresAttributes": [
{
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.21",
"targetId": null,
"targetType": null
}
],
"taskDefinitionArn": "your task definition arn",
"networkMode": "bridge",
"status": "ACTIVE",
"revision": 3,
"taskRoleArn": null,
"containerDefinitions": [
{
"volumesFrom": [],
"memory": null,
"extraHosts": null,
"dnsServers": null,
"disableNetworking": null,
"dnsSearchDomains": null,
"portMappings": [
{
"hostPort": 3306,
"containerPort": 3306,
"protocol": "tcp"
},
{
"hostPort": 3307,
"containerPort": 3307,
"protocol": "tcp"
}
],
"hostname": null,
"essential": true,
"entryPoint": null,
"mountPoints": [
{
"containerPath": "/var/lib/mysql",
"sourceVolume": "dbdata",
"readOnly": null
}
],
"name": "maria",
"ulimits": null,
"dockerSecurityOptions": null,
"environment": [
{
"name": "MYSQL_DATABASE",
"value": "db"
},
{
"name": "MYSQL_PASSWORD",
"value": "db_pw"
},
{
"name": "MYSQL_ROOT_PASSWORD",
"value": "root_pw"
},
{
"name": "MYSQL_USER",
"value": "db_user"
}
],
"links": null,
"workingDirectory": null,
"readonlyRootFilesystem": false,
"image": "mariadb:latest",
"command": [
"mysqld",
"--character-set-server=utf8",
"--collation-server=utf8_general_ci"
],
"user": null,
"dockerLabels": null,
"logConfiguration": null,
"cpu": 0,
"privileged": null,
"memoryReservation": 500
}
],
"placementConstraints": [],
"volumes": [
{
"host": {
"sourcePath": "/ecs/dbdata"
},
"name": "dbdata"
}
],
"family": "mariadb"
}
고민
Container 를 Task 별로 생성해야하는데, 그럼 Task JSON 에서 link 옵션을 연결할 수가 없다. 이 경우엔 어떻게 Task Definition 을 짜야되나? EC2 에 접근해서 매번 link 를 생성해서 다시 올려야되나?
이 부분을 해결하기 위해선 ecs-task-kite를 사용하거나 VPC 를 구성해 수동으로 연결해 주는 방법 밖에 없다.
쉬운 방법으로 가자면 DB 는 (모든 컨테이너가 하나의 데이터를 바라봐야하는) RDS나 ElastiCache처럼 AWS 의 서비스 사용하고 VPC 를 구성해 Backend, Frontend 단의 서버만 ECS Task 를 만들어서 가변적으로 돌리는 게 좋아보인다.
물론 동기화를 할 수도 있는데... 삽질할 시간에 더 잘 나온 포스팅을 기다려본다.
여담
그냥 모니터링 컨테이너 하나 더 띄우고, HAProxy 컨테이너 올리고 EC2 에 다 때려박고 싶다.
AWS Korea week in review에 소개되었다.