PWA

0. 앞선 글.

 

많은 이들이 모던 웹 개발하면 PWA와 SPA를 손꼽습니다. 실제로 제 글에서도 그렇게 소개를 했고요. 그렇게 알고 넘어갔는데 웹을 돌아다니던 중 다음과 같은 글을 발견했습니다.

 

https://itnext.io/the-state-of-progressive-web-apps-adoption-by-developers-8b119783d3b9

 

The State Of Progressive Web Apps Adoption By Developers

Are PWA preached or adopted by developers? Do they use these despite the lack of support on iOS? Here are some hints I gathered with polls…

itnext.io

 

눈이 갈 수밖에 없는 글이었습니다. PWA가 좋다곤 하는데 과연 얼마나 많은 개발자들이 PWA를 구현해서 사용하고 있을까요? 실제로 궁금하여 글을 읽고 내용을 공유하기 위해 한글 번역글을 남깁니다.

 

 

 

1. 프로그레시브 웹 앱의 채택 상태.

 

PWA가 개발자에 의해 전파되거나 선택되었나요? 그들은 iOS에서의 지원이 부족함에도 불구하고 PWA를 하나요? 지난 며칠간의 조사에서 수집한 몇 가지 힌트가 여기 있습니다.

 

Photo by YTCount on Unsplash

 

이번 주 "Apple vs Hey"이야기에 이어서 이러한 이슈에 대한 해결책으로 볼 수 있는 PWA가 대부분 개발자들에게 전파되었거나 실제로 이미 채택되었는지가 궁금했습니다. 

 

이런 나의 질문에 답하기 위해 트위터에서 설문조사를 실시하였고 이 새 블로그 게시물에 여러분과 공유하고 싶은 몇 가지 흥미로운 사실을 알게 되었습니다.

 

 

 

2. 한계점.

 

전 통계의 전문가도 아니고 조사 결과를 해석하는 전문가도 아닙니다. 또한 이 조사는 트위터에서 이루어졌으며 SNS를 통해 답변을 받았으므로 아마 충분하지 않을 수도 있습니다.

 

그러므로 이 글에서 읽은 내용을 당연하다고 생각하지는 말아주세요. 이글의 수치와 의견들을 더도 말고 덜도 말고 그저 힌트로만 봐주시기 바랍니다.

 

또한 저는 PWA의 열렬한 애호가입니다. 공정성을 유지하기 위해 노력할 수 있지만 동등한 경우에는 아마 PWA에 대해 부정적이기보단 긍정적일 겁니다.

 

 

 

3. 47%의 개발자는 PWA를 사용하지 않습니다.

 

나는 DEV.to의 PWA를 거의 매일 이용하고 있으며 트위터도 그러려고 하고 있습니다. 그러나 난 내가 내 핸드폰에서 매주 다른 것을 사용하고 있다고 생각하지 않았습니다. 그것이 처음 이 질문에 관심을 갖고 선택한 이유입니다.

 

놀랍게도 개발자의 47% 는 홈 화면에 설치된 PWA를 사용하고 있지 않습니다.

 

많은 개발자가 브라우저와 함께 PWA를 사용하고 있기 때문에 실제로는 숫자가 낮아야 한다고 생각하는 경향이 있지만 이 숫자는 꽤나 중요합니다.

 

물론 다음 장에서 볼 수 있듯이 이러한 낮은 비율의 주된 이유는 iOS에서 PWA에 대한 적절한 지원이 없기 때문입니다.

 

그럼에도 불구하고 이건 여전히 두 명의 개발자 중 거의 한 명이 PWA를 사용하지 않는다는 것을 의미하며 이는 개인적으로 모바일 기기에서 기술의 미래에 대해 약간의 걱정을 갖게 만듭니다. 응용프로그램을 프로그래밍하는 개발자가 응용프로그램을 사용하지 않거나 사용할 수 없다면 많은 사람들이 어떻게 받아들일까요?

 

반면에 긍정적인 점은 유리잔의 반은 비어있다기 보단 차 있다는 것입니다. 개발자 중 3명 중 1명꼴인 29% 는 매주 두 개 이상의 PWA를 사용합니다. 

 

https://bit.ly/3gbFUOI

 

 

또 흥미로운 점은 일부 개발자들은 PWA를 홈 화면에 고정해 두지 않을 채 데스크톱에서 여러 PWA를 사용하고 있다는 것입니다.

 

이것은 다음과 같은 생각으로 이어졌습니다: PWA의 미래가 실제로 모바일 폰이 첫 번째 장소가 아니라 데스크톱이었다면 어떨까요? 실제로 데스크톱이 모바일보다 먼저 인기를 얻으려면 어떻게 해야 할까요? 만약 이런 가정이 사실이면 구글의 크롬북은 적절했을까요? 아니면 구글은 크롬북을 믿고 PWA를 추진했을까요?

 

아마 너무 과한 해석과 과장이지만 난 정말로 이런 식으로 발전할지 안 할지를 알고 싶어 미래를 기다리고 있습니다. 

 

 

 

4. PWA를 사용하지 않는 사람들의 63%는 iOS를 사용합니다.

 

위에서 말했듯이 iOS의 부분적인 지원은 개발자가 PWA를 사용하지 않는 주된 이유입니다. PWA를 사용하지 않는 이들 중 63 %는 iPhone을 가지고 있습니다. 이는 모든 개발자의 30 %가 단순히 Apple에서 만든 전화를 가지고 있기 때문에 PWA를 사용하지 않는 것을 의미합니다.

 

https://bit.ly/31ufIuM

 

부분적인 지원보다 제가 받은 의견에 따르면 개발자들은 애플이 자동으로 "홈 화면에 추가하기"를 보여주지 않기 때문에 iOS에서 PWA 사용을 포기하는 것 같습니다. 애플은 Apple은 다른 브라우저를 통해 이러한 기능을 애플 기기에 구현하지 못하게 했습니다. 

 

나는 그것을 시도했고 맞았습니다. UX는 약간 실망스럽지만 iOS 홈 화면에 PWA를 설치할 수 있습니다. 관심이 있으시면 다음 트윗대로 진행하세요.

 

https://bit.ly/2VvkT9L

 

홈 화면에 웹 사이트를 추가할 수 있지만 적절한 PWA인 웹 사이트만 독립 실행형 앱으로 작동합니다. 정확한 피드백에 대해 Julio에게 감사드립니다.

 

나는 안드로이드에서 Firefox 모바일의 "홈 화면에 추가"UX를 시도해 보았습니다. 믿거나 말거나 그것이 실제로 최고라고 생각합니다. 크롬은 훌륭하지만 파이어 폭스는 나의 손을 이끌리게 했으며 "이봐 David 내가 여기 PWA를 네 폰에 올바르게 추가하는 방법을 보여줄게"라고 말하는듯한 느낌이 들었습니다.

 

파이어폭스의 디자이너가 이 글을 읽게 될지 모르겠지만 축하합니다. 대단한 일입니다!

 

https://bit.ly/2Vzl4Bc

 

5. 개발자의 8%가 Google Play의 앱을 좋아합니다.

 

모든 개발자의 30%가 iPhone으로 인해 PWA를 사용하지 않는다 하더라도 PWA 친화적인 안드로이드 폰을 소유하고 있는 17%의 개발자들 역시 PWA를 사용하지 않습니다. 이것이 마지막 설문조사를 실시한 이유입니다. 왜일까요?

 

가능한 해결책에 대해 생각하는데 시간이 걸렸으며 내 제안으로 설문 조사를 너무 편향적으로 만들지 않을까 걱정했습니다. 아마 오픈된 질문을 하는 게 더 나을 겁니다.

 

안드로이드에서 PWA를 사용하지 않는 전체의 7%에서 대부분의 개발자인 44%는 스토어나 Google Play의 UX나 디자인이 좋다고 느끼기 때문에 PWA를 사용하지 않습니다. 

 

솔직히 말해서 나는 이러한 사실을 해석하는 방법을 모르겠습니다. 내게도 같은 이유로 나쁜 기본 앱만큼 못생기고 성능이 좋지 않은 웹 애플리케이션이 있을 수도 있습니다. 나는 그게 개념과 실행에 관한 것이라고 생각합니다. 기술에 관계없이 나쁘게 구현되거나 디자인된 경우 결국에는 놀랍지 않을 것입니다.

 

https://bit.ly/2ZqJDRI

 

주목할만한 점: PWA가 저가형 장치에 가장 적합하다고 언급한 피드백에 따라 KaiOS가 PWA를 지원하는지 궁금합니다. 맞춰볼까요? KaiOS은 PWA를 지원할 뿐만 아니라 이를 스토어에 게시할 수도 있습니다.

 

 

 

6. 결론

 

PWA의 미래는 데스크톱일까요? 아니면 KaiOS 매장과 Google Play에 모두 게시할 수 있는 모바일 기기가 미래가 될까요? 아니면 언젠가 EU가 애플을 PWA 친화적으로 만들까요? 그 누가 알겠습니까...

 

그러나 확실히 나는 몇 가지 흥미로운 힌트를 배웠으며 앞으로도 PWA를 믿습니다.

 

또한, 저는 2021년 6월 달력에 이런 조사를 다시 실시하기 위해 미리 알림을 추가했습니다. 내년에는 이 주제가 어떻게 진화했는지 살펴보겠습니다.

 

당신의 의견과 피드백을 기다리겠습니다. 

 

David

 

 

 

7. 맺음글

 

글이 오롯이 PWA의 채택률에 집중되어 있지 않다는 생각이 들긴 하지만 설문으로 얻어진 수치만을 보더라도 충분의 의미가 있다고 생각합니다.

 

모두가 모던 웹의 SPA와 PWA를 말하고 있지만 실제로 아직 많은 사람이 쓰고 있진 않단 걸 알 수 있는 글이었습니다. 

 

David의 말대로 내년에도 이와 같은 조사가 수행된다면 어떻게 바뀔지 궁금합니다. 

 

 

 

 

 

 

 

 

Cloud환경이 아닌 Self-managed로 Jira를 설치하는 방법에 대해 알아봅니다. 이 글에선 Ubuntu server 20.04 버전에 Jiral를 설치해 보도록 하겠습니다.

 

 

 

0. 사전 준비.

 

Ubuntu server 20.04 환경을 미리 준비합니다.

 

 

 

1. Jira Software 다운로드.

 

여기에서 Jira Software를 다운로드합니다. 서비스될 운영체제에 맞게 다운로드해주시면 되며 이 글에선 Ubuntu server에 설치할 것이므로 Linux 64 bit를 선택해 다운로드해 줍니다.

 

 

다운로드가 끝나면 파일을 서버로 옮겨주세요.

 

 

 

2. Jira 설치.

 

서버로 접속 해 설치 파일을 복사한 경로로 이동합니다.

 

 

다음 명령어로 복사한 설치 파일에 권한을 부여합니다.

 

$ sudo chmod a+x atlassian-jira-software-8.10.0-x64.bin

 

실행 권한이 부여되면 다음 명령어로 설치 파일을 실행시켜줍니다.

 

$ sudo ./atlassian-jira-software-8.10.0-x64.bin

 

 

Jira는 OpenJDK를 필요로 합니다. Y를 입력해 Jira를 설치하면서 OpenJDK가 같이 설치되도록 합니다.

 

 

설치 준비 과정을 잠시 기다린 후 위와 같은 메시지가 나오면 엔터를 눌러줍니다.

 

 

설치 옵션을 골라줍니다. 첫 설치 환경이고  잘 모르겠으면 기본 설정을 사용해 줍시다. 1을 입력하세요.

 

 

앞서 선택한 옵션에 따라 Jira가 설치될 때 참고할 설정입니다. 확인 후 엔터를 눌러주세요.

 

 

설치가 끝나면 Jira를 바로 실행할 건지 묻습니다. 엔터를 눌러 바로 실행시키도록 합니다.

 

 

설치가 완료되었습니다. 이제 나머지 구성을 진행해 봅시다.

 

 

 

3. MySQL 설치.

 

제가 가장 삽질한 부분입니다. 2020.06.25 현재 Ubuntu Server 20.04로 mysql-server를 설치하면 8.0.20 버전이 설치됩니다. 별도 커넥터도 설치하고 구성 파일도 변경해봤으나 이 버전은 Jira에서 자꾸 에러 메시지를 던집니다.

 

공식적인 답변을 확인한 결과 MySQL8의 지원은 Jira 이슈에 공식적으로 등록된 상태이며 6일 전인 2020.06.19에 개발이 시작되었다고 댓글이 달렸습니다.

 

https://jira.atlassian.com/browse/JRASERVER-67359

 

[JRASERVER-67359] Add support for Jira to use MySQL 8 - Create and track feature requests for Atlassian products.

Problem Definition Current versions of Jira support several database types/versions, however Jira does not currently support MySQL 8.0 per the Supported platforms - Atlassian Documentation. For MySQL, only 5.5 -> 5.7 versions are currently supported. It wo

jira.atlassian.com

 

결론적으로 이 글이 작성되는 시점에선 MySQL 8 버전은 지원하고 있지 않으며 적절한 드라이버를 사용해도 Jira는 오류를 반환합니다. 따라서 우리는 5.7 버전의 MySQL을 설치해 보도록 하겠습니다.

 

먼저 현재 리포지토리에 MySQL 5.7 버전이 있나 확인해 봅시다.

 

$ sudo apt-cache mysql-server

 

 

슬프게도 등록되어 있지 않습니다. 그 말은 우리가 별도로 다운로드 해와야 한다는 뜻이죠. 다음 작업을 차근차근 따라 해 봅시다. 먼저 deb 파일을 받습니다.

 

$ sudo wget https://dev.mysql.com/get/mysql-apt-config_0.8.12-1_all.deb 

 

 

받은 deb 파일을 확인하고 실행시킵니다.

 

$ sudo dpkg -i mysql-apt-config_0.8.12-1_all.deb

 

 

위의 명령어를 실행하면 MySQL에 대한 apt 설정을 할 수 있는 화면이 보입니다.

 

 

Ubuntu bionic을 선택합니다.

 

 

MySQL Server & Cluster를 선택합니다.

 

 

MySQL 5.7을 선택합니다.

 

 

구성이 MySQL 5.7로 바뀐 것을 확인한 뒤 OK를 선택합니다. 이후 업데이트 후 다시 MySQL을 검색해 봅니다.

 

$ sudo apt-get update

$ sudo apt-cache policy mysql-server

 

 

이제 5.7 버전이 보입니다. 이 버전을 설치합시다. mysql-client를 설치하지 않고 mysql-community-server를 설치하면 client를 먼저 설치하라는 메시지와 함께 server 설치가 실패합니다. client를 먼저 설치해줍시다.

 

$ sudo apt install -f mysql-client=5.7.30-1ubuntu18.04

$ sudo apt install -f mysql-community-server=5.7.30-1ubuntu18.04

 

server 설치를 수행하게 되면 다음과 같은 화면에 보입니다.

 

 

루트 유저의 비밀 번호를 입력합니다. 설치가 완료되면 다음 명령어로 제대로 5.7 버전이 설치되었는지 확인합니다.

 

$ sudo mysql --version

 

 

 

 

3. MySQL DB 생성.

 

이제 MySQL에 Jira가 사용할 DB와 유저를 생성해 봅시다.

 

다음 명령어로 MySQL에 접속합니다.

 

$ sudo mysql -u root -p

 

 

먼저 Jira를 위한 DB를 생성합니다. 다음 쿼리를 실행시켜 주세요.

 

> CREATE DATABASE jira CHARACTER SET utf8mb4;

 

 

이제 이 DB에 유저를 생성한 뒤 권한을 부여해 줍니다.

 

> CREATE USER 'jira'@'localhost' IDENTIFIED BY 'P@SSWORD';

> GRANT ALL PRIVILEGES ON jira.* TO 'jira'@'localhost';

> FLUSH PRIVILEGES;

 

 

이제 새로 생성한 유저로 다시 mysql에 접속해 확인해 보도록 합니다.

 

$ sudo mysql -u jira -p

> show databases;

 

 

로그인도 정상적으로 되고 DB도 생성된 것을 확인하였습니다.

 

 

 

4. JDBC 설치 및 MySQL 설정 변경.

 

다음으로 Jira가 MySQL에 붙을 수 있도록  MySQL JDBC 드라이버를 설치해야 합니다. 우리는 5.7.30 버전의 MySQL을 설치했으므로 이 버전에 맞는 커넥터를 다운로드하여야 합니다. 여기에서 커넥터를 다운로드하도록 합니다. 이 글을 따라 하셨다면 다음과 같이 설정 후 다운로드하시면 됩니다.

 

 

어떤 걸 다운해도 상관없습니다. 어차피 압축을 풀고 그 내부의 파일을 사용해야 합니다. 다운로드한 파일을 압축 해제 한 뒤 폴더 내에 있는 "mysql-connector-java-5.1.49-bin.jar" 파일만 서버로 옮겨줍니다.

 

이제 이 jar 파일을 jira 설치 경로 내의 lib 폴더로 이동시켜 줍니다.

 

$ ls

$ sudo cp mysql-connector-java-5.1.49-bin.jar /opt/atlassian/jira/lib/

$ ls /opt/atlassian/jira/lib

 

 

그 후 해당 파일에 읽기 권한을 부여합니다.

 

$ sudo chmod 755 /opt/atlassian/jira/lib/mysql-connector-java-5.1.49-bin.jar

 

추가한 커넥터를 사용할 수 있도록 jira를 재시작합니다. jira start/stop 명령어는 jira를 설치한 방식에 따라 달라질 수 있습니다. 아래의 명령어는 linux installer를 통해 설치했을 때 사용할 수 있는 명령어임을 참고해 주세요.

 

$ sudo /etc/init.d/jira stop

$ sudo /etc/init.d/jira start

 

다음은 MySQL의 설정 파일을 변경해 줄 차례입니다. MySQL의 기본 설정값을 사용하게 되면 Jira가 신나게 경고 메시지를 띄워대니 그냥 Jira 설정에 맞춰서 변경합니다.

 

MySQL의 설정 파일은 /etc/mysq/my.cnf입니다. 우선 이 설정 파일의 백업본을 생성해 줍니다.

 

$ cd /etc/mysql

$ sudo cp my.cnf my.cnf.bak

 

 

그 후 my.cnf에 다음 설정을 추가합니다.

 

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_bin
default-storage-engine=INNODB
max_allowed_packet=256M
innodb_log_file_size=2GB
transaction-isolation=READ-COMMITTED
binlog_format=row

 

$ sudo vi my.cnf

 

 

** 만약 my.cnf에 "sql_mode = NO_AUTO_VALUE_ON_ZERO"가 있다면 이 옵션은 삭제해 주세요.

 

설정 파일을 변경하였으면 MySQL을 재시작합니다.

 

$ sudo service mysql restart

 

 

 

5. Jira 초기 설정 및 라이선스 활성화.

 

Jira가 설치된 서버에 8080 포트로 접근하면 다음과 같은 Jira 초기 설정 화면을 확인할 수 있습니다.

 

 

** 우측 상단의 "언어"를 클릭하면 한국어 옵션을 사용할 수 있습니다.

 

"사용자를 위해 설정하십시오."옵션은 별다른 추가 설정 없이 Trial 라이선스로 사용해 볼 수 있도록 해주는 옵션입니다. 이 옵션은 별도의 DB를 사용하지 않고 내장 DB를 사용하며 Atlassian의 계정과 연동해 알아서 설정을 구성해주고 Trial 라이선스를 발급받을 수 있는 화면으로 이동해 바로 라이선스 발급 절차를 진행할 수 있도록 해줍니다.

 

사전 준비사항에 언급한 대로 이 글에선 MySQL을 사용해 구성해 보도록 하겠습니다. "직접 설정하겠습니다"를 선택해주세요.

 

아래와 같이 설정한 DB 및 계정 정보를 입력한 후 테스트 연결 버튼을 클릭해 봅시다.

 

 

 

버튼을 클릭했을 때 아래와 같은 메시지가 보이면 연결에 성공한 것입니다.

 

 

** 만약 드라이버를 못 찾는다면 커넥터를 다시 한번 확인해 주시고 권한을 수정했는지 확인해 주시기 바랍니다.

** 만약 MySQL 5.7+ 설정에 관한 오류가 생기면 my.cnf 파일을 다시 확인해 주시기 바랍니다.

** 위의 두 과정 모두 jira 및 mysql의 재시작이 필요합니다.

 

이제 Jira가 구성될 때까지 느긋하게 기다려주세요.

 

 

왜 다시 영어로 돌아간 건지는 모르겠지만 구성이 완료되면 위와 같은 화면이 보입니다.

 

Application Title은 말 그대로 Jira의 타이틀입니다. 원하는 값으로 설정해 주세요.

Mode는 유저 생성에 관련된 설정값입니다. Private로 선택하면 회원가입이 막히고 Admin이 직접 추가한 유저만 사용할 수 있습니다.

BaseURL은 jira가 서비스될 URL을 입력해 줍니다.

 

 

이제 라이선스 키를 입력해 줄 차례입니다. 여기로 이동해 jira를 구입한 계정으로 로그인 한 뒤 라이선스 정보를 확인합니다.

 

라이선스를 클릭하면 오른쪽에 "License Key View License"와 같은 항목이 있습니다. "View License"를 선택해주세요.

 

 

위에 적혀있는 서버 ID를 입력한 후 Save를 클릭합니다. 그러면 "View License"에 생성된 라이선스 코드가 보이게 됩니다. 이 값을 jira의 "Your License Key"에 붙여 넣어 주고 Next 버튼을 클릭합니다.

 

 

이제 관리자 계정을 생성해 줄 차례입니다. 원하는 대로 생성해 주세요.

 

 

다음은 이메일 알림 관련 설정입니다. 이 내용은 추후 별도의 포스팅으로 다루겠습니다. Later를 선택한 후 Finish 버튼을 클릭합니다.

 

잠시 기다리면 구성이 끝나고 Jira가 시작됩니다. 사용할 언어를 다시 한국어로 변경합니다. 마지막으로 아바타 설정을 마치면 이제 Jira를 사용할 수 있습니다!

 

 

이제 "샘플 프로젝트 생성"을 선택해서 미리 잘 짜여 있는 예시를 볼 수도 있고 "새 프로젝트 만들기"를 선택해서 바로 자신만의 프로젝트를 구성할 수도 있습니다.

 

 

 

 

 

 

 

이번에 JIRA 사용을 테스트해 볼 일이 생겨 MySQL을 설치할 일이 생겼습니다. 이 글에서 Ubuntu Server 20.04에 MySQL을 설치하는 방법에 대해 알아봅니다.

 

 

 

0. 사전 준비

 

미리 Ubuntu Server 20.04 환경을 준비해 둡니다.

 

 

 

1. MySQL 설치.

 

다음 명령어로 서버에 MySQL을 설치합니다.

 

$ sudp apt-get update

$ sudo apt-get install mysql-server

 

 

 

2. MySQL Secure Installation 실행.

 

MariaDB와 다르게 MySQL은 apt-get 설치후에 추가 설치 과정이 필요합니다. 다음 명령어로 Secure intallation을 진행합니다.

 

$ sudo mysql_secure_installation

 

 

암호 검증에 관한 물음입니다. Y를 입력해 줍시다.

 

 

암호 복잡도에 관한 정책입니다. 2를 입력해 줍니다.

 

 

루트 계정의 암호를 입력해줍니다. 입력한 암호의 보안 강도가 측정되며 이 값을 사용할 것인지 묻습니다. Y를 입력합니다.

 

 

MySQL은 기본으로 익명의 유저를 갖으며 배포 환경에선 이 유저를 지워야 합니다. 아예 설정에서부터 지우도록 합니다. Y를 입력합니다.

 

 

일반적으로 root유저는 localhost에서만 접속이 가능합니다. 이 부분은 사용 환경에 맞춰서 진행해 주세요. 전 JIRA에서만 사용할 용도로 설치하기 때문에 Y를 입력해 원격지에서 접속을 막도록 하겠습니다.

 

 

MySQL은 기본적으로 누구나 접근 가능한 "test" DB를 생성합니다. 이 DB 역시 배포 환경에선 지워져야 할 대상이므로 설정에서부터 지우도록 합니다. Y를  입력합니다.

 

 

테이블 권한을 다시 불러와 줍니다. Y를 입력합니다.

 

이제 모든 설정이 끝났습니다. 다음 명령어로 직접 MySQL에 접속해 봅니다.

 

$ sudo mysql -u root -p

 

 

정상적으로 DB에 접속되는것을 확인할 수 있습니다.

 

 

 

 

 

 

Ubuntu server 20.04에 Redmine을 설치하는 법에 대해 알아봅니다.

 

 

 

0. 사전 준비.

 

우분투 서버를 미리 설치해 둡니다. 우분투 서버에 접속 해 apt-get update를 진행해 주세요.

 

 

 

1. MariaDB 설치 및 Database 구성.

 

Redmine은 MariaDB를 사용합니다. Redmine 설치에 앞서 MariaDB를 설치해 줍시다.

 

$ sudo apt-get install mariadb-server

 

MariaDB가 설치되면 이제 Database를 생성해줍시다.

 

$ sudo mysql -u root -p

> CREATE DATABASE redmine CHARACTER SET utf8mb4;

 

 

DB 생성 후 redmine DB에 유저를 생성한 뒤 권한을 부여해 줍니다.

 

> GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost' IDENTIFIED BY 'P@SSWORD';

> FLUSH PRIVILEGES;

 

 

생성한 유저로 로그인해서 DB가 제대로 생성되었는지 확인합니다.

 

$ mysql -u redmine -p

> SHOW DATABASES;

 

 

 

 

2, Apache 설치.

 

Redmine을 서비스하기 위해 필요한 제품을 설치합니다.

 

$ sudo apt-get install apache2 libapache2-mod-passenger

 

 

 

3. Redmine 설치.

 

이제 Redmine을 설치합니다.

 

$ sudo apt-get install redmine redmine-mysql

 

설치를 진행하다 보면 "Configuring redmine" 화면이 나타납니다. 설정을 진행해 줍시다.

 

OK를 선택합니다.

 

Yes를 선택합니다.

 

 

관리자 암호를 입력해줍니다.

 

 

다시 한번 입력하도록 합니다. 이후 설치가 완료되면 다음 명령어를 통해 bundler를 설치합니다.

 

$ sudo gem update

$ sudo gem install bundler

 

 

설치 완료 후 Apache passenger 모듈의 설정 파일을 다음과 같이 수정합니다.

 

$ sudo vi /etc/apache2/mods-available/passenger.conf

 

<IfModule mod_passenger.c>
  PassengerDefaultUser www-data
  PassengerRoot /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini
  PassengerDefaultRuby /usr/bin/ruby
</IfModule>

 

 

이제 www에 Redmine의 심볼릭 링크를 생성해 줍니다.

 

$ sudo ln -s /usr/share/redmine/public /var/www/html/redmine

 

 

 

4. Apache2에 Redmine 구성파일 생성.

 

/etc/apache2/sites-available 폴더에 Redmine.conf 파일을 생성 한 뒤 다음과 같이 작성합니다.

 

$ sudo vi /etc/apache2/sites-available/redmine.conf

<VirtualHost *:80>
  ServerAdmin admin@example.com
  DocumentRoot /var/www/html/redmine
  ServerName projects.example.com
  ServerAlias www.projects.example.com
  <Directory /var/www/html/redmine>
    RailsBaseURI /redmine
    PassengerResolveSymlinksInDocumentRoot on
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

 

 

만약 도메인 이름을 아직 정하지 못했거나 없다면 위의 방식 대신 아래의 방식을 사용해 IP주소를 통해 Redmine에 액세스 할 수 있습니다.

 

위와는 다른 000-default.conf 파일을 수정합니다.

 

$ sudo vi /etc/apache2/sites-available/000-default.conf

 

<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  DocumentRoot /var/www/html/redmine
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  <Directory /var/www/html/redmine>
    RailsBaseURI /redmine
    PassengerResolveSymlinksInDocumentRoot on
  </Directory>
</VirtualHost>

 

 

이제 Apache의 www-data 사용자가 액세스 할 수 있도록 Gemfile.lock을 생성하고 권한을 부여해 줍니다.

 

$ sudo touch /usr/share/redmine/Gemfile.lock

$ sudo chown www-data:www-data /usr/share/redmine/Gemfile.lock

 

구성 파일대로 Redmine을 활성화합니다.

 

$ sudo service apache2 restart

 

이제 호스트나 IP주소를 입력해 웹사이트로 이동해 봅시다.

 

 

정상적으로 Redmine이 활성화된 것을 확인할 수 있습니다. 이제 Redmine에서 프로젝트에 대한 설정을 한 뒤 사용하시면 됩니다.

 

기본 Admin계정 및 암호는 admin/admin입니다.

 

 

 

 

 

 

앞선 글에서 Express를 이용해 클러스터를 구성해 여러 워커를 가진 서버를 띄워보았습니다. 이번에는 제작한 웹 서버를 이용해 이중화 구성을 한 뒤 nginx를 이용해 로드밸런서를 만들어 그럴듯한 운영 환경을 구성해 보도록 하겠습니다.

 

 

 

0. 사전 준비.

 

이 글에서는 앞선 글에서 작성한 웹 서버를 사용합니다. 이전 글을 참고해 웹 서버를 준비해 주세요.

Nginx를 사용할 예정이니 미리 서버를 다운로드하여 주셔도 좋습니다. 단, Docker를 이용하실 분은 별도의 Nginx를 준비하지 않으셔도 됩니다.

모든 결과물을 한번에 배포하기 위해 docker-compose를 사용할 예정입니다. docker-compose를 준비해 주세요.

 

 

 

1. Nginx를 이용한 이중화 구성.

 

이제 우리가 만든 서버는 여러 개의 워커를 갖고 도커 위에서 동작합니다. 이제 안정성을 위해 두 개의 서버를 올려두고 그 앞에 Nginx를 둬서 로드밸런서의 역할을 하도록 합시다.

 

우리가 할 일은 단지 nginx의 config파일을 추가하는것 뿐 입니다. 아무런 추가 작업을 진행하지 않은 채 설정 파일을 추가해 주는 것만으로도 로드밸런서를 구성할 수 있습니다.

 

새로운 폴더인 my-nginx를 생성하고 그 안에 nginx 폴더를 만들어줍니다. 그리고 그 안에 nginx.conf 파일을 작성합니다.

 

#./nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

 

사실 위의 설정파일은 nginx를 설치하면 가장 기본으로 들어가 있는 값입니다. 스킵하셔도 무방합니다만 나중에 수정된 값을 사용할 수 있으므로 넣어주었습니다.

 

설정 파일을 살펴봅시다. nginx.conf의 맨 아래 include를 보세요. 이 include가 의미하는 바는 /etc/nginx/conf.d폴더 내의 모든 conf파일을 가져와 http 아래에 두겠다는 뜻입니다.

 

따라서 우리가 다음에 작성할 설정 파일이 바로 아래 추가될 것이란 의미가 됩니다. 이 외에도 nginx의 default 페이지가 담긴 default.conf도 추가될 것입니다. 

 

 

그다음으로nginx 안에 conf.d 폴더를 생성 한 뒤 my-react-lb.conf파일을 작성합니다.

 

# ./nginx/conf.d/my-react-lb.conf

upstream my-react {
    #least_conn;
    #ip_hash;
    server localhost:3000 weight=10 max_fails=3 fail_timeout=10s;
    server localhost:3001 weight=10 max_fails=3 fail_timeout=10s;
}    
server {
    listen                8080;
    server_name  localhost;
    location / {
        proxy_pass http://my-react;
    }
}

 

이 파일의 폴더 경로도 실제 리눅스 시스템에서 nginx가 위치하는 폴더의 경로를 맞추기 위함이므로 임의의 폴더에 작성하셔도 됩니다.

 

server항목을 먼저 봅시다. 이 서버는 8080 포트에서 동작하고 리버스 프록시로 동직 합니다. 즉, localhost:8080으로 들어오면 내부적으로 my-react로 연결시킨다는 뜻입니다. 이때 연결하는 my-react가 위에서 정의한 upstream입니다.

 

upstream 내부에 두 개의 서버가 정의되어 있습니다. 유저가 들어올 때마다 번갈아 가면서 3000, 3001 포트에 올라간 서버가 접속을 수용할 겁니다. 정리하자면 localhost:8080으로 접속하게 되면 내부적으로 로드밸런싱을 거쳐 localhost:3000, locahost:3001로 이동하게 된다는 겁니다.

 

주석 처리한 least_conn은 번갈아 가면서 수용하는 게 아닌 현재 접속이 가장 적은 곳에 우선 접속할 수 있도록 하는 기능이며 ip_hash는 접속한 사용자의 ip를 해싱해서 같은 사용자는 같은 서버로 접속할 수 있게 해 세션 문제 등을 해결할 수 있도록 하는 기능입니다.

 

따라서 메인 서버에 위의 설정 파일을 갖는 nginx를 설치한 뒤 Docker로 3000, 3001 포트에 앞서 작성한 웹 서버를 두대 올려 둔 후 localhost:8080으로 접속하면 정상적으로 로드밸런싱이 동작할 겁니다. 

 

 

 

2. Docker compose를 이용해 한번에 배포하기.

 

 

사실 위와 같이 배포하기 위해서는 서버에 nginx를 직접 설치해야 하며 그 서버에서 Docker가 동작 해 웹서버가 돌아야 합니다. 즉, nginx가 돌아가는 localhost에 웹서버가 동작해야 한다는 뜻입니다.

 

서버에 접속해 nginx를 설치하고 conf 파일을 수정해준 뒤 그 서버에 docker를 설치하고 docker 명령어로 두 개의 웹서버를 실행시키는 과정이 필요하게 됩니다. 이것만 해도 귀찮아지기 시작합니다. 게다가 수작업이 들어가는 만큼 오류의 가능성도 높아집니다.

 

이제 좀 더 편하게 위 과정을 다음과 같이 변경할 겁니다.

  1. Nginx를 직접 설치하지 않고 Docker Image로 빌드 해 Docker 명령어로 배포한다
  2. 명령어를 미리 작성해 둬 한번에 여러 이미지를 배포한다.

그러기 위해선 docker-compose와 nginx를 docker image로 빌드하는 작업이 필요합니다. 우선 nginx를 docker image로 빌드하기 위해 Dockerfile을 작성합니다.

 

# ./Dockerfile

FROM nginx:stable

COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./nginx/conf.d/my-react-lb.conf /etc/nginx/conf.d/my-react-lb.conf

CMD ["nginx", "-g", "daemon off;"]

 

매우 간단합니다. nginx 안정화 버전을 받아 우리가 작성한 conf파일을 복사 한 뒤 실행합니다. 이제 다음 명령어로 docker image를 빌드합니다.

 

$ docker build . -t my-nginx:0.0.1

 

그리고 빌드한 이미지를 실행해 봅시다. 

 

$ docker run -itd -p 3000:3000 my-react:0.0.1

$ docker run -itd -p 3001:3000 my-react:0.0.1

$ docker run -itd -p 8080:8080 my-nginx:0.0.1

 

localhost:8080으로 이동하면 어떻게 될까요?

 

 

에러가 보이는 이유에 대해 생각해 봅시다. 우리는 nginx를 도커로 돌렸으며 my-react-lb.conf의 upstream에 server로 localhost:3000와 localhost:3001을 등록했습니다. 과연 nginx 컨테이너 내에서 이 두 서버에 접속할 수 있을까요?

 

궁금하신 분은 다음 명령어로 한번 테스트해보시기 바랍니다.

 

$ docker exec -u 0 -it (my-nginx 컨테이너 ID) bash

$ apt-get update

$ apt-get intall telnet

$ telnet localhost 3000

 

당연히 독립된 네트워크 이므로 접속을 할 수 없습니다.

 

 

그러므로 우리는 Docker compose를 이용해야 합니다. Docker compose는 같이 배포되는 컨테이너끼리 미리 정의된 이름으로 접속이 가능합니다. 마치 도메인 네임처럼 말입니다.

 

최상위 폴더로 이동해 docker-compose.yml 파일을 생성합니다 저 같은 경우엔 my-react와 my-nginx를 포함하고 있는 폴더에 작성하였습니다.

 

 

그리고 docker-compose.yml을 다음과 같이 작성해 줍니다.

 

# docker-compose.yml

version: "3"
services:
    my-react-A:
        image: my-react:0.0.1
        ports:
            - "3000:3000"
    my-react-B:
        image: my-react:0.0.1
        ports:
            - "3001:3000"
    nginx:
        image: my-nginx:0.0.1
        ports:
            - "8080:8080"

 

여기서 서비스 항목을 봅시다. 우리는 my-react-A, my-react-B, nginx 이렇게 총 세 개의 컨테이너를 생성하도록 작성하였습니다. 당연히 이름은 변경해도 됩니다. 서비스 내의 image는 도커 이미지를 의미합니다. ports는 docker run의 -p 옵션과 동일합니다. 

 

이런 식으로 docker-compose를 구성하게 되면 위의 세 컨테이너 간에는 서비스에 작성한 이름으로 서로 접근이 가능해집니다. 따라서 우리의 my-react-lb.conf가 바뀌어야 함을 의미하죠.

 

# ./nginx/conf.d/my-react-lb.conf

upstream my-react {
    #least_conn;
    #ip_hash;
    server my-react-A:3000 weight=10 max_fails=3 fail_timeout=10s;
    server my-react-B:3000 weight=10 max_fails=3 fail_timeout=10s;
}    
server {
    listen                8080;
    server_name  localhost;
    location / {
        proxy_pass http://my-react;
    }
}

 

그리고 다시 도커 이미지를 빌드해 줍니다.

 

$ docker build ./my-nginx -t my-nginx:0.0.2

 

잊지 마세요. 태그에 버전을 0.0.2로 변경하였으므로 당연히 docker-compose를 변경해야 합니다. 이제 docker-compose를 통해 컨테이너를 올려보겠습니다.

 

$ docker-compoase up -d

 

우리가 작성한 docker-compomse파일 대로 컨테이너가 실행되었습니다. 이제 localhost:8080으로 이동해 보세요. react페이지가 보이시나요? 

 

그렇다면 우리가 어떤 서버로 접속했는지 알아보기 위해 localhost:8080/where로 이동해 봅시다.

 

 

위와 같이 접속할 때마다 서버 정보가 변경되는 것을 확인할 수 있습니다. 실제 운영환경에선 이렇게 하면 서버가 바뀌며 세션정보가 유실되므로 앞서 설명한 ip_hash와 least_conn옵션을 켜고 서버를 배포해야 합니다.

 

 

 

 

 

 

 

서버 운영에 있어서 성능과 안정성은 매우 중요한 요소들 중 하나입니다. 이 글에선 Express 서버를 이용할 때 여러 워커를 클러스터 구성을 이용해 서비스하고 NGINX를 이용해 이중화 구성을 하는 방법에 대해 알아보도록 합니다.

 

 

 

0. 사전 준비.

 

이 글에서 웹서버는 React와 Express를 사용해 배포한 서버를 사용할 예정입니다. 실제 배포는  Docker를 사용해 도커 이미지를 생성한 후 실행시키도록 할 예정입니다. 미리 NPM와 Docker를 준비합시다.

마지막으로 모든 작업 결과물을 한번에 구동시키기 위해 Docker compose를 사용할 예정이니 Docker compose도 준비해 주세요.

 

 

 

1. NodeJS

 

 

NodeJS는 구글이 구글 크롬에 사용하려고 제작한 V8 오픈소스 자바스크립트 엔진을 기반으로 제작된 자바스크립트 런타임입니다. NodeJS는 다음과 같은 특징이 있습니다.

  1. 단일 스레드.
  2. 비동기 방식.
  3. 이벤트 루프를 사용.
  4. NPM.

NodeJS는 싱글 스레드이기 때문에 하나의 CPU를 여럿이 나눠 갖는 건 비효율적입니다. 따라서 CPU 숫자에 맞춰서 서버를 띄워보겠습니다.

 

 

 

2. 웹서버 생성.

 

 

우선 프론트를 준비합니다. 다음 명령어로 react app을 생성해 주세요.

 

$ npx create-reac-app my-react

 

이 앱을 Express 서버로 서비스할 예정입니다. 폴더로 들어가서 다음 패키지를 설치하세요.

 

$ cd my-react

 

$ npm i express express-favicon 

 

이제 express 서버를 실행할 코드를 작성해야 합니다. server 폴더에 index.js파일을 만드신 후 다음과 같이 코딩해 주세요.

 

//// ./server/index.js

const express = require('express');
const favicon = require('express-favicon');
const path = require('path');

const app = express();
const port = 3000;
const server = app.listen(port, () => {
    console.log(`Server is listening on port ${server.address().port}.`);
});

app.use(favicon(path.join(__dirname, '../public/favicon.ico')));
app.use(express.static(__dirname));
app.use(express.static(path.join(__dirname, '../build')));

app.get('/ping', (req, res) => {
    return res.send('pong');
});
app.get('/*', (req, res) => {
    return res.sendFile(path.join(__dirname, '../build', 'index.html'));
});

 

이제 기본으로 생성된 react를 빌드해 줍시다.

 

$ npm run build

 

여기까지 수행하셨으면 폴더 구조가 다음과 같을 겁니다.

 

 

우리가 작성한 express서버가 react app을 잘 서빙하는지 확인해 봅시다. localhost:3000으로 이동해 react 앱을 확인해 보세요. 그리고 localhost:3000/ping으로 이동해 pong 메시지를 잘 수신하는지도 확인합시다.

 

 

 

3. 클러스터 구성.

 

앞서 설명한 대로 NodeJS는 단일 스레드입니다. 이제 우린 서버의 CPU개수에 맞춰서 여러 워커를 띄어서 서버를 운영해 보도록 하겠습니다.

 

여러 워커가 돌아갈 때 워커가 동작하는 서버를 구분하기 위해 uuid 패키지를 설치합니다.

 

$ npm i uuid

 

그리고./server/index.js를 다음과 같이 수정해주세요.

 

//// ./server/index.js
const cluster = require('cluster');
const os = require('os');
const uuid = require('uuid');

const port = 3000;
const instance_id = uuid.v4();

//// Create worker.
const cpu_count = os.cpus().length;
const worker_count = cpu_count / 2;

//// If master, create workers and revive dead worker.
if(cluster.isMaster){
    console.log(`Server ID: ${instance_id}`);
    console.log(`Number of server's CPU: ${cpu_count}`);
    console.log(`Number of workers to create: ${worker_count}`);
    console.log(`Now create total ${worker_count} workers ...`);

    //// Message listener
    const workerMsgListener = (msg) => {
        const worker_id = msg.worker_id;
        //// Send master's id.
        if(msg.cmd === 'MASTER_ID'){
            cluster.workers[worker_id].send({cmd: 'MASTER_ID', master_id: instance_id});
        }
    }

    //// Create workers
    for(var i = 0; i < worker_count; i ++){
        const worker = cluster.fork();
        console.log(`Worker is created. [${i +1}/${worker_count}]`);
        worker.on('message', workerMsgListener);
    }

    //// Worker is now online.
    cluster.on('online', (worker) => { console.log(`Worker is now online: ${worker.process.pid}`); });

    //// Re-create dead worker.
    cluster.on('exit', (deadWorker) => {
        console.log(`Worker is dead: ${deadWorker.process.pid}`);
        const worker = cluster.fork();
        console.log(`New worker is created.`);
        worker.on('message', workerMsgListener);
    });
}
//// If worker, run servers.
else if(cluster.isWorker){
    const express = require('express');
    const favicon = require('express-favicon');
    const path = require('path');

    const app = express();
    const worker_id = cluster.worker.id;
    const server = app.listen(port, () => { console.log(`Server is listening on port ${server.address().port}.`); });

    let master_id = "";

    //// Request master's id to master.
    process.send({worker_id: worker_id, cmd: 'MASTER_ID'});
    process.on('message', (msg) => {
        if(msg.cmd === 'MASTER_ID'){
            master_id = msg.master_id;
        }
    });

    app.use(favicon(path.join(__dirname, '../public/favicon.ico')));
    app.use(express.static(__dirname));
    app.use(express.static(path.join(__dirname, '../build')));

    app.get('/ping', (req, res) => { return res.send('pong'); });
    app.get('/where', (req, res) => { return res.send(`Running server: ${master_id} \n Running worker: ${worker_id}`); });
    app.get('/kill', (req, res) => { cluster.worker.kill(); return res.send(`Called worker killer.`);})
    app.get('/*', (req, res) => { return res.sendFile(path.join(__dirname, '../build', 'index.html')); });
}

 

소스가 길어졌으니 주석을 확인하면서 차근차근 코딩해 주세요. 간단히 설명해드리자면 다음과 같습니다.

  1. 우리는 한 서버의 CPU개수의 절반만큼의 워커를 생성할 겁니다.
  2. 만약 현재 생성된 클러스터가 마스터라면 워커 클러스터를 생성하고 관리하는 역할을 수행합니다.
  3. 마스터는 미리 정해진 개수만큼 워커를 생성하고 만약 죽은 워커가 발견된 경우 새 워커를 생성시켜 항상 일정 개수의 워커가 서버에서 동작할 수 있도록 합니다.
  4. 워커는 express 서버를 구동합니다.
  5. 워커에는 어느 서버에서 수행되고 있는지 확인할 수 있는 기능과 현재 워커를 죽일 수 있는 기능이 추가되었습니다.

코딩이 끝났다면 다시 서버를 실행시켜 보도록 합시다.

 

$ node server

 

콘솔에서 다음과 같은 메시지를 확인할 수 있습니다.

 

 

이제 localhost:3000/where로 이동해 보세요. 현재 우리가 접속한 서버와 워커의 번호를 확인할 수 있습니다. 여기서 localhost:3000/kill로 이동하면 워커를 죽일 수 있으며 이때 마스터는 죽은 워커를 확인 해 새 워커를 생성합니다. 

 

 

이후 다시 localhost:3000/where로 이동하면 워커의 번호가 변경된 것을 확인할 수 있습니다.

 

 

 

4. Docker를 이용한 서버 구동

 

 

이제 우리가 작성한 서버를 도커를 이용해 배포해봅시다. 간단히 배포하기 위해 Dockerfile을 사용해 배포할 예정입니다. 

 

Dockerfile 파일을 생성 한 뒤 다음과 같이 작성해 주세요.

 

# ./Dockerfile

FROM node:slim

# app 폴더 생성.
RUN mkdir -p /app

# 작업 폴더를 app폴더로 지정.
WORKDIR /app

# dockerfile과 같은 경로의 파일들을 app폴더로 복사
ADD ./ /app

# 패키지 파일 설치.
RUN npm install

# 환경을 배포 환경으로 변경.
ENV NODE_ENV=production

#빌드 수행
RUN npm run build

ENV HOST=0.0.0.0 PORT=3000
EXPOSE ${PORT}

#서버 실행
CMD ["node", "server"]

 

아주 간단한 Dockerfile을 작성했습니다. 주석을 참고하시면 모두 이해하실 수 있을 겁니다. 이제 다음 명령어를 통해 작성한 Dockerfile을 이용해 도커 이미지를 생성합니다.

 

$ docker build . -t my-react:0.0.1

 

 

도커 이미지가 정상적으로 생성된 것을 확인할 수 있습니다. 다음 명령어를 통해 직접 실행시켜 주세요.

 

$ docker run -itd -p 8080:3000 my-react:0.0.1

 

이제 localhost:8080로 이동하면 모든 기능을 정상적으로 사용할 수 있습니다.

 

 

 

 

 

 

 

기존에 사용하고 있던 Harbor에 LDAP를 연동해서 쓰고 있었는데 LDAP 서버가 문제가 생기는 바람에 더 이상 로그인이 불가능 한 상황에 이르렀습니다. 밀고 다시 설치할까 하던 중에 공식 홈페이지에 2.0.0 버전이 릴리즈 되었다는 사실을 알게 되어 이렇게 된 김에 버전을 올리기로 하였습니다. 

 

공식 홈페이지 글에 따르면 크게 세가지 변경점이 있다고 합니다.

  1. OCI 호환.
  2. Aqua Trivy를 기본 스캐너로 탑재.
  3. 다크 모드 지원.

 

 

다크 모드로 보다 세련되어 보이게 변경되었습니다. 맘에 드네요. 그럼 기존 설치한 Harbor 1.10.2 버전을 2.0.0 버전으로 업그레이드해 보도록 하겠습니다.

 

 

 

1. Harbor 요구사항 확인.

 

다행스럽게도 기존 요구사항이 2.0으로 오면서 변경되거나 하지는 않았습니다. Harbor의 요구사항은 다음과 같습니다.

 

Harbor는 리눅스에서 동작합니다. 기존 버전에서는 우분투 서버 18.04.3버전을 사용했습니다.

 

하드웨어 요구사항은 다음과 같습니다.

리소스 최소 사양 권장 사양
CPU 2 CPU 4CPU
메모리 4 GB 8 GB
스토리지 40 GB 160 GB

 

소프트웨어 요구사항은 다음과 같습니다.

소프트웨어 버전
Docker Engine 17.06.0 CE 버전 이상
Docker Compose 1.18.0 버전 이상
OpenSSL 최신 버전 권장

 

도커 설치는 이 글을, 도커 컴포즈 설치는 이 글을 참고해주세요.

 

네트워크 포트 사용은 다음과 같습니다.

 

포트 프로토콜 설명      
443 HTTPS Harbor 포털 및 코어 API는이 포트에서 HTTPS 요청을 수락합니다. 구성 파일에서이 포트를 변경할 수 있습니다.
4443 HTTPS 하버 용 Docker Content Trust 서비스에 대한 연결입니다. 공증(Notary)이 활성화 된 경우에만 필요합니다. 구성 파일에서이 포트를 변경할 수 있습니다.
80 HTTP Harbor 포털 및 코어 API는이 포트에서 HTTP 요청을 수락합니다. 구성 파일에서이 포트를 변경할 수 있습니다.




2. Harbor Installer 다운로드.

 

Harbor 릴리즈 페이지로 이동하여 원하는 버전을 다운받습니다. 여러 버전이 있지만 이 글에서는 2.0 버전으로의 업그레이드를 목적으로 하고 있기 때문에 2.0.0 태그의 버전을 다운받도록 하겠습니다. 별다른 문제나 요구가 없는 한 "harbor-offline-installer-v2.0.0.tgz" 파일을 다운로드하여주시면 됩니다. 

 

다운로드한 파일이 위치한 폴더로 이동한 뒤 다음 명령어로 압축을 풀어줍니다.

 

$ tar xvf harbor-offline-installer-v2.0.0.tgz

 

 

 

 

3. Harbor 사전 설정.

 

파일이 준비되었으면 설치할 때 적용될 설정 파일을 수정해야 합니다. 기존 harbor.yml에서 .tmpl이 추가된 것으로 보입니다. hatbot.yml의 탬플릿의 느낌 같네요. 직접 파일을 열어보면 아시겠지만 변경된 내용이 좀 있습니다.

 

우선 TLS관련 설정이 추가되었습니다. HTTPS 바로 아래에 추가되었으니 TLS 설정이 필요하신분은 건드리시면 될 겁니다.

공식 홈페이지에 따르면 이 기능에 대한 설명은 다음과 같습니다.

 

By default, The internal communication between Harbor’s component (harbor-core,harbor-jobservice,proxy,harbor-portal,registry,registryctl,trivy_adapter,clair_adapter,chartmuseum) use HTTP protocol which might not be secure enough for some production environment. Since Harbor v2.0, TLS can be used for this internal network. In production environments, always use HTTPS is a recommended best practice.
This functionality is introduced via the internal_tls in harbor.yml file. To enabled internal TLS, set enabled to true and set the dir value to the path of directory that contains the internal cert files.

기본적으로 Harbor 구성 요소 (하버 코어, 하버 작업 서비스, 프록시, 하버 포털, 레지스트리, 레지스트리) 간의 내부 통신은 일부 프로덕션 환경에 충분히 안전하지 않을 수 있는 HTTP 프로토콜을 사용합니다. Harbor v2.0부터 이 내부 네트워크에 TLS를 사용할 수 있습니다. 프로덕션 환경에서는 항상 HTTPS를 사용하는 것이 좋습니다.
이 기능은 internal_tlsin harbor.yml파일을 통해 도입되었습니다 . 내부 TLS를 활성화하기 위해서는 enabled값을 true로 설정하고 dir을 내부 인증 파일이 포함된 디렉터리 경로로 설정하세요.

 

또한 Trivy설정이 추가되었습니다. Trivy를 간단하게 설명하자면 다음과 같습니다.

 

A Simple and Comprehensive Vulnerability Scanner for Containers, Suitable for CI

CI에 적합한 컨테이너를위한 간단하고 포괄적인 취약점 스캐너

 

Trivy에 관심이 있으신 분은 여기를 참조해 주시길 바라며 이 기능은 추후 사용하게된다면 포스팅하겠습니다.

 

그 외의 설정은 모두 동일합니다. 이제 이 파일을 복사해 줍시다.

 

$ cp harbor.yml.tmpl harbor.yml

 

복사한 harbor.yml파일을 적절하게 수정해 줍시다. 다른 건 몰라도 hostname은 변경해야 합니다. 물론 실제 도커를 사용하기 위해서는 인증서도 적용해야 합니다.

 

$ vi harbor.yml

 

 

 

4. Harbor 설치.

 

이제 남은 건 인스톨 스크립트를 실행하는 것뿐입니다. 업그레이드를 하는 경우 기존에 돌아가고 있는 harbor를 내리는 것을 잊지 마세요. 

 

$ sudo docker-compose down -v

 

 

혹시 몰라서 구 버전의 harbor docker compose 파일이 존재하는 폴더에서 실행시켜줬습니다.

 

이제 새 버전의 harbor을 설치합니다.

 

$ sudo ./install.sh

 

 

쭉 설치가 진행되고 마지막에 성공적으로 설치되었으며 자동으로 시작되었다는 메시지가 출력됩니다.

 

이제 호스트로 이동하면 정상적으로 harbor에 접속할 수 있습니다.

 

 

 

 

 

 

우분투 서버에서 ssh의 기본 포트인 22번 대신 다른 포트를 사용하는 방법에 대해 알아봅니다.

 

 

 

0. 사전 준비.

 

이 글은 우분투 서버 20.04를 기준으로 작성되었습니다. 당연히 해당 서버에 미리 ssh가 설치되어 있다는 전제로 작업이 진행됩니다.

 

 

 

1. ssh 포트 변경.

 

ssh에 접속할 때 사용하는 포트는 설정 파일에 기재되어 있습니다. 다음 명령어를 통해 설정 파일을 수정합니다.

 

$ sudo vi /etc/ssh/sshd_config

 

설정파일을 열면 바로 포트 정보를 확인할 수 있습니다.

 

 

우리는 여기에 기본값인 22 대신 원하는 값을 넣어 준 뒤 저장하도록 합니다.

 

 

 

 

2. ssh 재시작

 

변경된 설정파일을 적용하기 위해 ssh를 재시작해 줍니다.

 

$ sudo service sshd restart

 

 

 

3. 접속 확인.

 

terminal이나 putty와 같은 프로그램을 이용해 접속을 시도해 봅니다.

 

 

기본 포트로 접속시 위와 같은 메시지를 확인할 수 있습니다.

 

 

변경된 포트로 접속시 정상적으로 접속되는 것을 확인할 수 있습니다.

 

 

 

 

 

 

Storybook 패키지에 대해 알아봅니다.

 

 

 

1. Storybook이란?

 

최근 프론트엔드 개발은 페이지 단위가 아닌 컴포넌트 단위로 개발된 다는 것을 들어본 적이 있을 겁니다. 프로젝트 내에서 컴포넌트를 개발하다 보면 초기에는 별 문제가 없지만 프로젝트의 규모가 커질수록 컴포넌트 개발에 독립성을 유지하기 힘들어집니다. 이런 경우 스토리북을 이용하면 보다 편하게 컴포넌트를 개발할 수 있습니다.

 

Build bulletproof UI components faster
Storybook is an open source tool for developing UI components in isolation for React, Vue, and Angular. It makes building stunning UIs organized and efficient.

 

스토리북의 공식 홈페이지를 방문하면 가장 먼저 우리를 반겨주는 문구입니다. 위의 설명대로 스토리북은 독립된 환경에서 컴포넌트를 개발하고 이를 확인해 볼 수 있는 오픈소스 라이브러리 입니다. 이러한 환경에서 개발된 컴포넌트는 독립성이 유지되기 때문에 자연히 재사용성이 좋아집니다. 또한 스토리북은 개발뿐 아니라 문서화, 테스팅에 대해서도 좋은 기능을 제공해 줍니다.

 

 

 

2. Storybook 사용해보기.

 

먼저 스토리북을 사용하기 위해 예시 프로젝트를 생성합니다. 이 글에서는 create-react-app을 통해 예시 프로젝트를 생성하겠습니다.

 

> create-react-app storybook-ex

> cd storybook-ex 

> npx -p @storybook/cli sb init --type react_scripts

 

여기까지 진행한 뒤 package.json파일을 확인합니다. script항목에 보면 전에 없던 스토리북에 관련된 스크립트를 확인할 수 있습니다. 

 

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "storybook": "start-storybook -p 9009 -s public",
    "build-storybook": "build-storybook -s public"
  },

 

이제 스토리북을 실행시켜 9009번 포트로 접속해 봅시다.

 

> npm run strorybook

> http://localhost:9009/로 이동.

 

 

정상적으로 스토리북이 실행된 것을 확인할 수 있습니다. 좌측에 "Welcom"과 "Button"이 보이시나요? 이것들을 스토리북에선 스토리라고 부릅니다. 스토리란 결국 우리가 컴포넌트를 구현하게 될 독립적 공간이 되는것 입니다. 

 

이제 다시 프로젝트로 돌아가서 위의 스토리들이 어디에 위치하는지 확인해 봅시다. ../.storybook 폴더 내의 main.js 파일을 열어보세요.

 

module.exports = {
  stories: ['../src/**/*.stories.js'],
  addons: [
    '@storybook/preset-create-react-app',
    '@storybook/addon-actions',
    '@storybook/addon-links',
  ],
};

 

스토리북에서 사용하는 애드온들과 스토리에 대한 설정이 정의되어 있습니다. stories에 설정된 값을 확인해보면 src 폴더 내에 있는 .stories.js로 끝나는 모든 파일을 스토리 파일로 인식하도록 설정되어 있습니다. 실제로 위의 "Welcom"과 "Button" 스토리들은 src폴더 내의 stories폴더에 존재하고 있습니다.

 

새로운 스토리를 생성하기 위해서는 src폴더 내 임의 폴더에 파일이름.stories.js로 파일을 생성 한 뒤 컴포넌트를 작성하면 됩니다.

 

 

 

 

 

 

1. CString to const char*

 

CString strText = _T("myStirng");
const char* ccText;
ccText = (CStringA)strText;

 

 

 

2. const char* to CString

const char* ccText = "myString";
CString strText;
ccText = (CString)strText;

 

 

 

 

+ Recent posts