iamcreepyrabbit-blog
iamcreepyrabbit-blog
괴토
15 posts
Don't wanna be here? Send us removal request.
iamcreepyrabbit-blog · 5 years ago
Text
Let’s encrypt 클라이언트 직접 구현하기
일반적으로 Let's encrypt로부터 SSL 인증서를 발급받기 위해 certbot처럼 이미 구현된 클라이언트를 이용한다. 이런 클라이언트들은 잘 만들어졌고 사용하기 편리하지만 동작을 내가 원하는대로 고칠 수 없다. Let's encrypt의 클라이언트를 직접 구현해보면서 ACME v2 프로토콜의 동작 흐름을 살펴보았다. 클라이언트가 인증서를 얻기까지는 다음과 같은 과정이 필요하다.
URL 목록 가져오기
초기 nonce 값 얻기
계정 생성 (Account 생성)
인증서 주문 (Order 생성)
도메인 권한 증명 (Authorization의 Challenge 만족시키기)
주문 마무리
인증서 다운로드
1. URL 목록 가져오기
먼저 ACME 서버의 Directory 객체를 GET 호출하는 것으로 시작한다. Directory는 이후 과정에서 호출해야하는 URL들을 담고 있다. Let's encrypt의 directory를 호출하는 주소는 https://acme-v02.api.letsencrypt.org/directory 다. Let's encrypt는 클라이언트를 테스트하는 용도로 사용할 수 있도록 staging 주소도 제공한다.
{ "newNonce": string "newAccount": string "newOrder": string }
"newNonce"는 2번 과정에 사용한다.
"newAccount"는 3번 과정에 사용한다.
"newOrder"는 4번 과정에 사용한다.
2. 초기 nonce 값 얻기
ACME 프로토콜에서 Directory 요청(1번 과정)과 newNonce 요청(2번 과정)을 제외한 모든 요청은 JWS header에 nonce 값이 있어야 한다. 리플레이 공격을 막기위해서다. JWS header가 무엇인지는 바로 아래에서 알아보자. 첫 nonce는 newNonce의 GET 호출 응답의 'replay-nonce' 헤더에서 얻을 수 있다. 이후 nonce는 바로 이전 호출의 응답의 'replay-nonce' 헤더에서 얻을 수 있다. 예를 들어 (과정이 순서대로 잘 진행된다면) 3번 과정 응답에서 얻은 nonce를 4번 과정 요청에 사용하는 식이다.
JWS와 POST 요청 body
Directory 요청(1번 과정)과 newNonce 요청(2번 과정)을 제외한 모든 요청은 JWS를 이용하여 요청자의 자격을 증명해야 한다. 요청의 body는 항상 아래와 같은 형태인데, 이런 형태를 JWS flattened object라고 부른다. 물론 body 전송을 위해 POST method를 쓴다.
{ "protected": string "payload": string "signiture": string }
JWT의 구조와 같다. JWT는 unprotected header를 사용하고, ACME에서 요구하는 JWS object는 protected header를 사용한다. 그래서 "protected"는 JWT의 header와 같은 역할이다. 하나씩 살펴보자.
"protected"
요청을 위한 protected header 객체를 JSON serialize한 후 base64url로 인코딩한 값이다.
protected header 객체는 아래와 같은 형태를 가진다.
{ "alg": "ES256" "nonce": string "url": string "jwk": { "crv": "P-256" "kty": "EC" "x": string "y": string } }
또는
{ "alg": "ES256" "nonce": string "url": string "kid": string }
"alg": 암호화에 사용할 알고리즘. 여러가지가 있는데 "ES256"을 추천하고 있다.
"nonce": 위에서 설명한 nonce 값.
"url": 현재 요청의 url.
"jwk": JWK 형태의 키 object.
"kid": Account의 URL.
사용할 Account의 URL을 모른다면 첫번째의 형태로 호출하고, 안다면 두번째 형태로 호출한다. "kid"의 값인 Account URL은 newAccount의 응답에 있다. 즉, newAccount 호출 이전에는 위의 형태를 사용하고, newAccount 호출 이후에는 아래 형태를 사용한다. "jwk"와 "kid" 둘 중 하나만 protected header에 있을 수 있다.
위에서 언급했듯이 이 객체를 JSON serialize한 후 base64url로 인코딩한다.
"payload"
서버에 전송하고 싶은 실제 데이터를 JSON serialize한 후 base64url로 인코딩한 값이다. 인코딩하기 전의 실제 데이터는 빈 문자열(""), 빈 객체({}), 또는 JSON 형태의 객체 중 하나다.
"signiture"
요청의 위변조 방지를 위한 값이다. ["protected" 값] + "." + ["payload" 값] 의 결과를 위의 "alg" 알고리즘으로 인코딩한 값이다.
위에서 "payload"는 빈 문자열("")일 수도 있다고 했다. 요청 시 자격 증명을 위해 요청 body에 JWS object를 보내야하기 때문에 반드시 POST method를 써야한다. 하지만 ACME 서버에서는 이렇게 "payload"가 빈 문자열인 요청을 마치 GET method 요청처럼 다뤄야하며, 이런 요청을 POST-as-GET 요청이라고 부른다.
3. 계정 생성 (Account 생성)
인증서 요청을 위한 계정을 만든다. 위에서 설명했듯이 여기서부터는 nonce와 JWS를 항상 요청할 때 사용해야한다.
newAccount 요청 payload는 아래와 같은 형태다.
{ "contact": string[] "termsOfServiceAgreed": true }
"contact": 생성할 Account의 연락가능한 URL 목록. 간단하게 "mailto:[email protected]"처럼 메일 url을 써넣으면 된다.
"termsOfServiceAgreed": 사용자가 ACME 서버의 terms of service를 동의하는지 여부. 클라이언트에서 자동으로 동의하지 않고, 인터렉션을 통해 사용자에게 동의를 받아야한다.
요청시 protected header에 "jwk"가 사용자의 고유성을 결정한다. newAccount 호출로 새로운 Account가 만들어졌다면 201을, 기존 Account를 업데이트 업데이트했다면 200을 응답한다.
newAccount 응답의 'location' 헤더에는 Account URL이 있다. 위에서 JWS의 구조에 대해 설명할 때 언급했듯이 이후 요청부터는 이 Account URL을 protected header의 "kid"에 사용한다.
4. 인증서 주문 (Order 생성)
인증서를 주문한다. 주문한다고 바로 인증서를 받는 것이 아니고 이후 과정이 복잡하기 때문에 여기서는 주문서만 작성한다고 생각하는게 이해하기 쉬웠다. 위에서 설명했듯이 여기서부터는 protected header에 "jwk" 대신 "kid"를 사용해야한다.
newOrder 요청 payload는 아래와 같은 형태다.
{ "identifiers": { "type": "dns", "value": string }[] }
"identifiers": 발급하고 싶은 인증서의 식별자 목록이다. "type"은 "dns"로 고정하고, "value"에 인증서 발급을 원하는 도메인을 넣으면 된다.
newOrder 호출시 서버는 201을 응답하며, 응답의 'location' 헤더에는 Order URL이 있다. Order URL을 호출하면 Order의 상태를 알 수 있다.
응답 body를 보면 "status"가 있다. Order는 5가지의 상태가 있다.
"pending": Order의 도메인 권한 증명을 기다리는 중.
"ready": Order의 도메인 권한 증명을 완료했고, Order를 마무리짓기(finalize)를 기다리는 중. finalize는 주문서를 제출한다는 의미로 이해했다.
"processing": Order가 마무리되었고(finalized), 인증서 생성 중. "valid"`: Order의 인증서를 다운로드할 수 있는 상태.
"invalid": Order가 반려됨.
응답 body에서 일단 확인해야하는 두가지 키는 이렇다.
{ "status": "pending" "authorizations": string[] ... }
authorizations에는 각 identifiers에 해당하는 Authorization 객체의 URL이 담겨있다. Authorization 객체는 위에서 인증서 주문시 전달한 도메인들의 권한 증명 상태를 나타내고 있다.
5. 도메인 권한 증명 (Authorization의 Challenge 만족시키기)
Authorization URL로 POST-as-GET을 호출하면 이런 형태의 응답을 받는다.
{ "status": "pending" "challenges": { "type": string "status": "pending" "url": string "token": string }[] ... }
어떤 Challenge도 충족되지 않았다면 Authorization는 'pending' 상태다. 이 글에선 "http-01" 타입의 Challenge를 수행할 것이다.
먼저 JWK thumbprint를 만들자. newAccount 호출시 protected header에서 사용했던 "jwk" 객체를 JSON serialize하고 SHA-256으로 해싱한 후 base64url로 인코딩한다.
그리고 도메인과 연결한 서버에서 http://{domain}/.well-known/acme-challenge/{challenge.token}으로 접속할 때 "{challenge.token}.{JWK thumbprint}"를 응답하도록 한다.
이제 'http-01' 챌린지를 충족할 준비가 끝났다. ACME 서버에 우리가 준비가 되었다는 것을 알려주자. Challenge URL로 빈 객체를 payload로 담아 보내자. 빈 문자열("")이 아니라 빈 객체({})다. 그러면 해당 Challenge는 "processing" 상태가 된다.
ACME 서버가 Challenge를 확인할 때까지 기다리자. 확인이 끝나면 이 Challenge 상태는 "valid" 또는 "invalid"가 된다. 그동안 클라이언트는 Challenge의 상태가 바뀔 때까지 Challenge URL을 계속 POST-as-GET으로 풀링해야한다. POST-as-GET이므로 payload는 빈 문자열("")이다.
Challenge의 상태가 "invalid"라면 상위의 Authorization과 Order의 상태 또한 "invalid"로 바뀐다. 클라이언트는 여기서 인증서 발급 과정을 종료해야한다.
Challenge의 상태가 "valid"로 바뀐 것을 확인했다면, 다음은 Authorization의 상태가 "valid"로 바뀔 때까지 기다리자. 클라이언트는 Authorization의 상태가 바뀔 때까지 Authorization URL을 계속 POST-as-GET으로 풀링해야한다. 만약 newOrder 요청시 여러개의 도메인을 주문했다면 모든 Authorzation이 이 과정을 거쳐야 한다. 또 Authorzation의 상태가 "valid"로 바뀐 것을 확인했다면, Order의 상태가 "ready"로 바뀔 때까지 기다리자. Order URL은 newOrder 요청시 'location' 헤더에 있었다. 클라이언트는 Order의 상태가 바뀔 때까지 Order URL을 계속 POST-as-GET으로 풀링해야한다.
6. 주문 마무리
Order의 도메인 권한 증명을 완료했다. 이제 도메인 발급까지는 finalize 한 단계만 남았다. finalize는 주문서를 제출한다는 의미로 이해할 수 있다. Order 객체에서 "finalize" URL을 볼 수 있다.
{ "status": "ready" "finalize": string ... }
Order를 finalize하기 위해선 CSR(Certificate Signing Request)이 필요하다. CSR은 openssl을 통해 만들 수 있다. CSR를 만들 때 사용하는 키는 발급 받은 인증서를 사용할 서버에서도 필요하다.
openssl genrsa 4096 > privkey.key openssl req -new -sha256 -key privkey.key -subj "/CN=domain.com" -outform DER > csr.der
그 다음 finalize URL로 이 CSR를 전달하자. finalize URL은 Order URL을 호출했을 때 볼 수 있었다. payload의 형태는 이렇다.
{ "csr": string }
"csr": 위의 csr.der 파일을 base64url로 인코딩한 값을 넣어준다.
finalize 후 Order의 상태가 "valid"로 바뀔 때까지 기다리자. 클라이언트는 Order의 상태가 바뀔 때까지 Order URL을 계속 POST-as-GET으로 풀링해야한다.
7. 인증서 다운로드
Order의 상태가 "valid"로 바뀌면 Order 객체에서 "certificate"을 확인할 수 있다.
{ "status": "valid" "certificate": string }
"certificate"는 인증서를 다운로드 받을 수 있는 URL이다. 마지막으로 이 URL에 POST-as-GET을 호출해 인증서를 받자.
블로그를 바꾸면서 기술적인 내용은 적지 않으려고 했는데, 재미있는 경험이라 공유하고 싶었다. Tumblr는 기술 블로그 하기엔 에디터가 끔찍하다.
0 notes
iamcreepyrabbit-blog · 5 years ago
Text
결제 서비스 선택하기
서비스에 결제 기능을 붙히기 위해 어느 결제 서비스가 적절할 지 찾아봤다. 해외 카드를 받을 수 있어야 하기 때문에 국내 결제 서비스는 고려도 하지 않았다. 요구사항은 이렇다.
한국 계좌나 호��� 계좌로 송금할 수 있어야 한다
호주 계정 생성시 ID를 필수로 받지 않아야 한다. 나에겐 호주 계좌는 있지만, ID가 없다.
정기 결제를 지원해야 한다. 직접 구현해 본 경험은 있어서 만드려면 만들 순 있겠지만, 이미 기능을 제공한다면 그걸 그냥 이용하는게 좋다.
수수료가 낮을수록 좋다. 돈을 못 벌어도 요금을 지불해야하는 Chargebee같은 서비스는 제외.
UI를 커스터마이징할 수 있으면 좋다. 완벽히 못한다면 최대한 할 수 있는게 좋다.
Paypal
수수료: 국내 결제일 때 2.9% + $0.30, 국외 결제일 때 4.4% + $0.30
국내 결제라는 말은 내 페이팔 계정과 결제 카드가 같은 국가여야 저 수수료라는 의미다.한국 내 결제가 아니다. 페이팔은 솔루션의 종류가 많다. 어느 것을 적용해야 적절한 지 보고 선택해야 하는데, 이게 너무 귀찮다. 사용자가 꼭 페이팔 계정이 있지 않아도 된다. 하지만 정기 결제 기능을 사용하려면 사용자가 반드시 paypal 유저여야 하는 것 같다. 문서만 봤을땐 RESTful API도 제공하는데, 페이팔이 제공하고 있는 데모는 전부 결제시 paypal hosted page를 통한다. 결국 UI는 커스터마이징할 수 없다.
Stripe
수수료: 2.9% + $0.30
지원하는 국가의 수가 적다. 하지만 호주 계정을 만들 때 ID를 받지 않는다. UI 커스터마이징도 거의 완벽하게 가능하다. 모든 것이 개발자 친화적이다. 문서가 매우 잘 되어있고, SDK도 깔끔하다. 결제 기능을 연동할 때 어떤 흐름으로 개발하는지 잘 알고 있는 것 같다.
Billsby
수수료: $50000까지는 무료. 이후부터 0.8%
신생 회사다. 오픈 전에 Product hunt에서 보고 뉴스레터를 등록했더니, 최근 베타 코드가 날아와서 이것도 고려해봤다. UI를 커스터마이징할 수 없었지만, 수수료가 너무 싸서 확인해봤더니 목록의 다른 회사와는 다르게 카드 정보를 다른 PG사를 통해 저장하고 있다. PG사가 아니라서 이런 방식으로 우회한 것 같다. 수수료를 연결된 PG사와 Billsby 양쪽 모두 내야하는 것인지 확실하지 않아서 보류했다.
Braintree
수수료: 미국계정 2.9% + $0.30. 호주계정 1.75% + $0.30 AUD
Stripe만큼 좋다. 그러나 호주 계정을 만들때도 ID를 필수로 입력 받는다.
paddle
수수료: 5% + $0.50
수수료도 비싼데, UI를 커스터마이징할 수 없다. Indie hackers에서는 Stripe를 쓰다가 Paddle로 넘어간 사람이 여러명 보이는데, 비싼 수수료에도 불구하고 이용하는 다른 장점이 있을지도 모르겠다.
2checkout
수수료: 정기 결제는 4.5% + $0.45
기능마다 수수료가 다르다. 비싼 수수료만 보고 리서칭 종료.
결제시 통화는 글로벌 결제의 표준으로 봐도 무방한 USD로 받기로 했다. 호주 계좌와 연동해서 Stripe를 사용할 경우에는 국내 계좌로 출금까지 USD -> AUD -> KRW로 두 번의 환전이 있다. 반면 Paypal을 사용한다면 Paypal USD 계좌에서 바로 출금할 수 있으므로 USD -> KRW로 한 번의 환전이 있다.
결국 Stripe를 써보기로 결정했다. 미국 외 결제 비율이 낮고, Paypal UI의 사용자 경험이 나쁘지 않다면 Paypal이 더 나은 선택일지도 모른다. 하지만 Paypal의 정기 구독 모델은 사용자에게 Paypal 계정을 만들도록 강제한다. 그래서 환전 수수료가 조금 더 나오더라도 사용자 경험을 완벽히 통제하는 게 더 낫다고 판단했다.
0 notes
iamcreepyrabbit-blog · 5 years ago
Text
타자기 치는 원숭이
수 많은 행성들 가운데 지구에 생명체가 생긴 이유는 이 행성이 뭘 잘 했기 때문이 아니다. 우주에는 무수히 많은 경우의 수가 존재한다. 그 중 일부가 우연히 생명을 탄생시킬 수 있는 환경에 있었고, 지구는 그 중 하나일 뿐이다. 우리 인류가 과거에 무언가 열심히 노력했기 때문에 높은 지능을 가지도록 진화하게 된 것이 아니다. 현생 인류 이전까지 수 많은 돌연변이가 있었고, 높은 지능을 가진 돌연변이가 환경에 더 잘 적응할 수 있었다. 우리는 긴 시간동안 지능 향상 돌연변이가 축적된 결과다.
이런 생각은 백 년 전 '무한 원숭이 정리'로 처음 제시되었다. 원숭이에게 타자기를 주고 아무거나 입력하게 시킬 때, 무한한 수의 원숭이와 무한한 시간이 주어진다면 프랑스 국립 박물관의 모든 책을 정확하게 찍어낼 것임이 거의 확실하다는 정리다. 여기서 '거의 확실하다'는 확률의 극한 값이 100%라는 의미다.
'무한 원숭이 정리'에서 첫 책을 찍어낸 원숭이가 또 다른 책을 찍어낼 확률이 다른 원숭이보다 높을까? 아니다. 최근 세렌디피티(우연한 과학적 발견)의 극대화를 위해 실험실 예산을 어떻게 배분해야하는지를 탐구하는 연구가 있었다. 연구는 모든 실험실에 예산을 골고루 분배했을 때 세렌디피티가 가장 높아진다는 결과를 보여주었다. 무작위로 랜덤하게 배분하는 방법도 답이 아니였고, 과거 성과가 좋은 실험실에 집중해서 배분하는 방법도 답이 아니였다.
시도의 횟수가 무한하면 아주 작은 확률이라도 반드시 발생한다. 시도의 횟수가 적당히만 많아도 평범하지 않은 결과가 발생한다. 그럼에도 불구하고 우리는 운을 감지하지 못한다. 결과의 원인을 계속 분석하며 패턴을 찾으려고 한다.
0 notes
iamcreepyrabbit-blog · 5 years ago
Text
모든 것은 만들어진 것이다
나사에서 1966년 발행한 Assessing Technology Transfer에는 이런 구절이 있다.
수명의 관점에서 인류의 마지막 5만년을 보면, 진보의 속도를 쉽게 알 수 있다. 5만년의 시간은 800명의 수명으로 환산된다. 그러나 이 800명 중 650명은 동굴 또는 그보다 못한 곳에서 살았고, 마지막 70명만이 타인과 효과적으로 소통할 수 있는 수단을 가졌으며, 마지막 6명만이 인쇄물을 보거나 온도를 측정할 수 있었고, 마지막 4명만이 시간을 정확히 잴 수 있었고, 마지막 2명만이 전기모터를 사용했다. 우리의 물질 세계를 구성하는 대부분의 것들은 마지막 800번째 사람의 수명 내에 개발된 것들이다.
언어와 문자, 종교, 이념과 사상, 경제 체제, 사회 시스템, 지명과 도로와 건물, 사람의 생애주기까지 모든 것들이 의도했든 하지 않았든 우리와 같은 누군가가 만들어낸 것이다. 그것도 아주 최근에 만든 것들이다. 지금까지 우리는 시간의 길이를 과장해서 생각했기 때문에 이런 것들이 전통이 있고, 절대적인 가치라고 생각한다. 14세기 고려 말기를 살던 사람은 지금 우리와 매우 먼 것처럼 느껴지지만, 실제로는 아주 작은 시간적 차이 때문에 만나지 못했다. 하지만 이들과 우리는 사는 방식과 환경이 매우 다른데, 이것은 우리를 둘러싼 모든 것들이 생긴지 얼마 되지 않았음을 보여주는 증거다.
인류의 역사는 아직 무척 짧다. 인류 최초의 건물은 약 12000년 전에 세워졌는데, 지금 우리가 사용하는 달력의 연도에 10000을 더하면 인류 진보의 시간과 맞아 떨어진다. 올해를 2020이 아니라 12020년으로 보면 인류의 역사에서 지금 우리가 어디 쯤 있는지 조금 더 알기 쉽다. 자연적으로 존재했고, 지금까지 항상 있어왔으며, 영원히 변하지 않는 것은 없다. 우리가 당연하다 여겼던 모든 것들은 결국 우리가 만든 것이다. 그럼에도 우리의 생각과 행동은 이 틀에 갖혀있다.
0 notes
iamcreepyrabbit-blog · 5 years ago
Text
확률과 인간의 마음
Google play music과 같은 음악 재생 프로그램에는 당신이 여��껏 들었던 음악을 기반으로 재생목록을 만들어주는 기능이 있다. Google play music의 경우 이 기능을 I’m feeling lucky mix라고 한다. 만약 앞서 들었던 음악이 없는 상태에서 이 기능을 이용하면 어떻게 될까? 프로그램은 완전히 랜덤하게 음악을 들려줄 수 밖에 없다. 첫 음악으로 재즈가 나왔다고 하자. 수동으로 음악을 선택하지 않고 계속 I’m feeling lucky mix로만 음악을 듣는다면 점점 재즈만 나올 확률이 높아질 것이다. 당신의 취향과 무관하게 말이다!
아주 오래전에 같은 내용의 글을 이전 블로그에 올렸었다. 지금 보면 이것은 카오스 이론에 대한 이야기다. 지프의 법칙과는 연관 짓기 어렵다. 하지만 조금 더 확장해서 생각해 볼 가치가 있는 현상이다.
로또 추첨은 매 회차가 독립시행이다. 앞선 회차의 결과들이 다음 회차에 전혀 영향을 미치지 않는다. 지금까지 36번 공이 많이 뽑혔다고 해서 이번주 토요일에 36이 또 나올 확률이 높아지지 않는다. 우리 주변에도 로또처럼 독립시행인 사건이 많다. 하지만 독립시행 사건에 사람의 마음이 개입하면서 종속시행이 된다. 로또로 치자면 36이 나올수록 36이 또 나올 확률이 점점 높아지게 변한다.
근근이 매출을 유지하는 식당이 방송에 나온 것도 아닌데 갑자기 손님이 많아진다. 손님의 방문이 독립시행에서 종속시행으로 변하는 순간이다. 이전에 왔다간 손님 중 누군가가 다른 사람에게 영향력을 미쳤기 때문이다. 해외여행을 갈 때, 맛집을 검색해서 가보면 온통 한국인 뿐이다. 누군가 우연히 아무곳이나 들렀고 자신의 블로그에 글을 남겼다. 사람들은 식당을 검색하고 이 글을 읽고 또 글을 남긴다.
주식차트도 비슷하다. 과거의 데이터는 미래 주가를 예측하는데에 아무런 도움이 되지 못한다. 하지만 누군가가 ‘이런 패턴일 때는 주가가 떨어진다'라고 한다면, 실제로 그런 패턴이 나타났을 때 사람들은 주가가 떨어질 것이라고 생각해서 주식을 판다. 사람들이 주식을 팔기 때문에 주가도 정말 떨어진다.
0 notes
iamcreepyrabbit-blog · 5 years ago
Text
파레토 법칙
흔히 80/20의 법칙이라고도 불리는 파레토 법칙은 결과의 80%는 원인의 20%로부터 일어나는 현상을 가리킨다. 파레토 법칙은 분야를 막론하고 거의 모든 곳에서 매우 다양한 스케일로 존재한다. 소프트웨어도 예외는 아니다. 제품 사용 시간의 80%는 전체 기능의 가장 핵심적인 20%에 집중되어 있다. 
나머지 80% 기능을 포기해도 괜찮다는 이야기가 아니다. 이 80%의 기능이 없다면 온전한 제품이라 부를 수 없다. 그러나 우리는 자원이 한정된 환경에 있기 때문에 효율을 고려해야한다. 핵심 기능은 조금만 개선되어도 사용자가 쉽게 눈치채는 반면, 나머지 기능은 갑자기 없어져도 잘 모른다. 잘 쓰이지도 않는 하위 80%의 기능을 개선하기보단 핵심적인 20%에 우리의 시간과 노력을 쏟는 게 낫다.
사이드 프로젝트는 극단적인 자원 부족 환경에 놓여있다. 처음부터 모든 사용자를 만족시키려고 다양한 선택지를 제공하다가는 아무것도 완성하지 못한다. 20%의 핵심 기능만 제공하는 초창기 제품은 거의 프로토타입에 가까울 수도 있을 것이다. 그러나 조금 부족하더라도 계속해서 제품을 개선하는 모습을 보여준다면 사용자들은 당장 나머지 80% 기능이 없더라도 같이 만들어나간다는 느낌을 줄 수 있을지도 모른다.
0 notes
iamcreepyrabbit-blog · 5 years ago
Text
가능한 작게 만들자
해커톤이 좀 익숙해지고 나서는 참가할 때마다 팀원들에게 최대한 작게 만들자고 제안한다. 욕심을 버리고 현실적으로 생각해서 뭐라도 데모 때 보여줄 수 있는 걸 완성하려면 이 방법 밖에 없다. 눈속임으로 동작하는 것처럼 보이는게 아니라 정말로 구현하는 것이기 때문에 예상치 못한 복병이 항상 숨어있다. 그래서 시간이 부족하다. 시간이 남는 경우는 거의 없다.
작게 만들자는 제안이 수용되지 않았는데 뭔가 완성된 경우는 없었다. 회의와 토론에 시간을 전부 쓰기 때문이다. 작게 만들기로 약속했더라도 얼마나 작게 만들 것인지는 조금씩 생각이 다른데, 이런 약속이 없다면 구현은 뒷전이고 정말 행사가 끝날 때까지 회의만 한다.
모든 것이 익숙하더라도 단 하나의 챌린지가 있으면 일이 어떻게 될 지 모른다. 해커톤에서는 모든 것이 챌린지다. 처음 보는 사람들끼리 만나 커뮤니케이션이 쉽지 않다. 팀원들의 기술 스택도 일정하지 않다. 마감까지 시간도 짧다. 환경도 열악하다. 와이파이 안 터지면 정말 망한다. 지금 만들고 있는 사이드 프로젝트도 정말 단 하나의 기능만 한다. 하지만 그 하나의 기능이 나에겐 상당히 도전적이다. 그래서 기술적인 문제 때문에 생각보다도 더 오래걸리고 있다.
완벽하고자 하는 욕심을 버려야한다. 내가 표현하고 싶은 가장 핵심적인 부분에만 집중하자. 제품을 더 좋게 할 수 있는 아이디어가 많지만, 핵심 가치에 직접적인 도움이 되지 않는다면 잠시 내려놓아야 한다.
0 notes
iamcreepyrabbit-blog · 5 years ago
Text
무엇을 만들 것인가
프로그래밍을 공부하면서 내가 학습한 것들은 '어떻게 만드는가'에 관한 것이다. 하지만 정말 어려운 것은 '어떻게 만드는가'가 아니라 '무엇을 만드는가'에 관한 것이다. 작년 한 해는 이걸 깨달은 해였다. 프로그래밍 독학을 막 시작했을 때는 이걸 할 줄 알면 뭐든지 할 수 있을 것 같았다. 하지만 사실이 아니다. 무엇을 만들어야 좋을지 모른다면 어떻게 잘 만들지 알고 있어도 무색하다.
'어떻게 만드는가'는 쉽게 배울 수 있다. 많은 사람들이 다양한 분야에서 이 고민을 함께 하고 있다. 여러가지 매체를 통해 지식을 습득할 수 있다. 학습의 내용도 상세하고 정확하다. 가끔 성공의 공식도 존재한다. 하지만 '무엇을 만드는가'는 배우기 어렵다. 두루뭉술하고 추상적이다. 이게 맞는 것인지 확신할 수 없다. 정말로 '무엇을 만드는가'를 잘 아는 사람조차도 이런 지식과 지혜를 제대로 전달하기 어려워하는 것처럼 보인다. 결국 스스로 생각하는 수 밖에 없는 것 같다.
프레임워크, 프로그래밍 언어, 디자인 툴 등 제품을 개발할 때 쓰는 많은 도구들은 점점 사용하기 편하고 개발자의 고민을 줄여주는 방향으로 발전한다. AWS, Google Cloud 등 클라우드 인프라 서비스들을 보면 이런 경향이 뚜렷하다. 초기 클라우드 서비스들은 가상 컴퓨팅만 제공하는 것으로부터 시작했지만 점점 더 관리된 형태(managed)의 컨테이너 기반 서비스를 출시하면서 인프라 운영에 대한 고민을 없애고 있다. 더 나아가 정말로 비즈니스 로직만 고민할 수 있도록 다양한 서비스들을 Serverless화 하고 있다. 심지어 코드 한 줄 없이도 서비스를 만들 수 있는 zero-code 서비스도 여러 클라우드 서비스에서 관심을 가지고 있다고 한다. (하지만 이것도 terraform 코드로 관리하겠지)
이런 경향은 결국 제품 개발의 진입장벽을 낮춘다. 과거에 비해 지금은 더 적은 지식으로도 제품을 만들고 출시할 수 있다. 시간이 흐를수록 '어떻게 만드는가'의 중요성은 작아질 것이다. 반면 사회가 복잡해지고 변화가 가속하면서 '무엇을 만드는가'에 대한 지식은 더 중요해진다. 사람들에게 매력적인 제품을 만들기 위해서는 무엇을 고민해야 할까?
0 notes
iamcreepyrabbit-blog · 5 years ago
Text
Yak shaving
원래 목적을 이루기 위해 다른 일을 먼저 하는 것을 야크 쉐이빙(Yak shaving)이라 한다. 요리를 하기 전에 먼저 설거지를 하거나, 시험공부에 집중하기 위해 방청소를 하는 것이 야크 쉐이빙이다.
한 단계정도의 야크 쉐이빙(A -> B)은 괜찮다. 여러 단계(A -> B -> C -> D)라면 문제가 있다. 본래 하고자 했던 일을 시작하기도 전에 지칠 수 있다. 원래 무엇을 하려고 했는지 까먹을 수도 있다. 더 큰 문제는 직접 일을 진행하면서 요구사항을 직면하기 전까지는 이 작업이 몇 단계의 야크 쉐이빙이 필요한지 알기 어렵다는 것이다. 두 단계라고 생각하고 시작했지만 네 단계, 다섯 단계일 수도 있다.
내가 쓰던 블로그를 그대로 두고 텀블러에서 새로 글을 쓰는 이유도 야크 쉐이빙을 없애는데에 목적이 있었다. 이전 블로그는 Github pages 위에서 호스팅했는데 글 쓰는 과정이 매우 길었다. 글을 하나 쓰려면
컴퓨터를 켜고
에디터를 켜고 (유튜브도 켜고)
메모장에서 글감을 가져와 붙혀넣은 뒤에
마크다운 문법이 올바른지 확인하고
git commit && git push
해야한다. 과정을 줄여보려고 netlify-cms를 블로그에 통합했지만, 속도가 느리고 과정도 별로 줄지 않았으며 모바일에서의 경험이 나빴다. 이전에 썼던 글들도 이미지가 깨져 수정해야한다. 그래도 Github 위에 블로그를 운영하는게 개발자로써 좀 더 있어보인다. 커스터마이징도 자유롭다. 이전 블로그를 계속 가지고 가면서 적용해볼 수 있는 이런저런 솔루션을 찾던 도중에 마음의 소리가 들렸다.
글 안쓰니?
뜨끔. 빠르게 글을 쓰자는 문제 해결에만 집중해 얼른 블로그 플랫폼 하나를 선택했다. 이제는 글감을 붙혀넣고 버튼만 누르면 된다. 모바일에서도 빠르게 글을 쓸 수 있다.
야크쉐이빙은 대부분 욕심이다. 꼭 하지 않아도 원래 목적을 달성할 수 있음에도 더 완성도를 높이고 싶은 마음에 일을 길게 늘어뜨린다. 일을 미루는 습관이 있다면 야크쉐이빙으로 포장할 수도 있겠다. 해야만 하는 일을 마주하기 두려워 곁가지만 계속 쳐내고 있지는 않은지 생각해보자.
0 notes
iamcreepyrabbit-blog · 6 years ago
Text
사이드 프로젝트를 하자
사이드 프로젝트를 하자. 업무나 회사일 따위가 아니라 우리가 모든 것을 결정하는 그런 프로젝트말이다. 사이드 프로젝트는 여러분의 결정에 대해 상사나 동료, 클라이언트에게 동의를 구할 필요 없고, 조율과 회의 대신 구상과 실험만 존재하는 아주 멋진 활동이다. 무엇을 할 지, 왜 하는지, 어떻게 할 지 모두 우리가 정하고 책임진다. 사이드 프로젝트의 가장 중요한 목표는 언제나 완성과 배포다. 완성하지 못했지만 되돌아보니 좋은 경험이 되었다고 얘기할 수도 있다. 그러나 제자리를 걸어도 신발은 닳는다. 그러므로 당신이 프로젝트를 완성할 확률을 최대한 높일 수 있도록 계획해야한다. 프로젝트를 완성할 확률을 높이려면 무엇을 만들 것인지, 왜 그것을 만들 것인지, 어떻게 만들 것인지, 이 세 가지를 잘 선택해야한다. 만드는데에 시간이 짧게 소요되는 프로젝트가 좋다. 이 프로젝트를 시작하게 만든 동기가 프로젝트의 완성까지 함께 할 수 있게 만들자. 진행 중에 갑자기 다른 일로 바빠지거나, 새로운 아이디어가 생각나서 지금 프로젝트에 흥미가 떨어진다거나, 번아웃될 수 있다. 이런 상황들은 언제 우리에게 찾아올지 모르고, 찾아오면 제어하기 어렵다. 그렇기 때문에 이런 것들이 찾아올 수 있는 시간을 줄여야한다. 위에서 언급한 무엇을, 왜, 어떻게 만들 것인지를 선택할 때 이 점을 고려하여 선택하자.
0 notes
iamcreepyrabbit-blog · 6 years ago
Text
Nov 2019, 재미있는 프로젝트들
mindustry
https://mindustrygame.github.io
샌드박스 타워디펜스 게임이다. Factorio와 조금 비슷하다. Java로 만들어졌고 오픈소스다. 윈도우, 맥, 리눅스 안드로이드에서 무료로 다운받아 플레이할 수 있다. iOS에서는 유료로 구매할 수 있다. 몇 시간 플레이해봤는데, 돈 받고 팔아도 손색없을만한 재미와 완성도를 느꼈다. 제작자도 커뮤니티와 계속 소통하며 로드맵을 정하고 있으며 프로젝트 진행도 매우 활발하다.
DevShop
https://github.com/secretGeek/devShop
칸반보드를 이용한 웹게임이다. 10년 전부터 가지고 있던 게임 아이디어라고 제작자는 밝히고 있다. 칸반보드가 무엇인지 알고 있다면 매우 쉽게 플레이할 수 있다. 튜토리얼 없이 플레이어가 게임을 진행하면서 학습할 수 있도록 구성한 점을 보아 게임 제작 경험이 많은 제작자 같다.
OpenDiablo2
https://opendiablo2.com/
디아블로2의 엔진을 Go로 다시 만드는 프로젝트다. 게임의 내용은 저작권이 있으므로 이 엔진으로 플레이하려면 원본 게임에서 직접 가져와야한다.
hendricius/pizza-dough
https://github.com/hendricius/pizza-dough
최고의 피자 도우를 만드는 방법을 설명한다. 제작자가 어릴 적 먹었던 피자를 재현하기 위해 끊임없이 관찰하고 사람들과 토론하고 실험하면서 얻은 지식을 설명해두었다. 더불어 간단히 도우의 재료량을 찾을 수 있는 계산기도 구현했다.
0 notes
iamcreepyrabbit-blog · 6 years ago
Text
나는 무엇인가
물리적으로 무엇을 '나'라고 부르는걸까? 먼저 내 신체를 '나'라고 부를 수 있겠다. 몸 안에 박은 철심이나 임플란트도 '나'에 포함할 수 있는 부분일까? 포스트 아포칼립스 작품들은 신체 일부를 기계로 대체하는 것이 보편화되어 있다. 만약 팔이나 다리가 하나 없는 신체라고 하더라고 여전히 '나'라고 부를 수 있을 것이다. 그렇다면 신체를 조금씩 계속 잘라낼 때 '내가 내가 아니게 되어버리는 순간(...)'은 언제일까? 언제까지 나라고 부를 수 있고, 언제부터 내가 아닌걸까? 잘려나간 신체는 더 이상 내가 아닌걸까?
개인의 정체성은 얼굴을 통해 형성된다고 한다. 수술이나 사고로 얼굴이 바뀐 사람은 이전 얼굴에 대해 그리움을 느끼는데, 이 그리움은 타인에 대한 그리움과 완전히 동일하다. 영화 뷰티 인사이드에서 자고 일어나면 모습이 바뀌는 우진은 실제 현실에선 제대로 된 생활을 영위하지 못할 것이다. 본인의 정체성에 대한 이미지를 그릴 수 없기 때문이다. 그렇다면 얼굴을 포함한 머리 부분이 '나'라고 부를 수 있는 최소한의 영역일까?
좌뇌와 우뇌는 뇌량이라는 부위를 통해 정보를 교환한다. 과거엔 간질 치료를 위해서 이 뇌량을 자르기도 했다. 그런데 이런 치료를 받은 사람들은 독특한 경험을 한다. 좌뇌와 우뇌가 각각의 의식이 있는 것처럼 행동한다. 두 뇌 모두 스스로를 '나'이라고 하면서 반대편의 뇌를 '나'와는 다른 존재로 여긴다. 뇌량을 자르기 전의 우리의 의식은 어디로 간 것일까? 누가 진짜 나일까?
0 notes
iamcreepyrabbit-blog · 6 years ago
Text
감각의 필터
우리는 세상을 우리의 몸을 통해 관찰한다. 눈으로 세상을 보고, 귀로 듣고, 코로 냄새를 맡고, 혀로 맛보고, 피부로 사물을 느낀다. 우리는 이 감각들을 통해서만 세상을 파악할 수 있다. 다르게 말하면 우리는 우리 몸에 갇혀있다. 우리는 눈을 통해 사물의 색을 알 수 있다. 우리 눈은 전자기파의 매우 일부분만 볼 수 있다. 이걸 빛(가시광선)이라고 부른다. 물체에서 반사된 가시광선을 통해 우리는 물체의 색깔을 인식한다. 그러나 실제로 사물은 아무 색도 없다. 우리가 특정 영역의 전자기파를 파란색이라 부르기로 약속했다. 하지만 이 파장을 뇌에서 시각적으로 처리하는 방식이 사람마다 다를 수 있다. 당신에게는 파란색으로 보이는 하늘이 나에겐 빨간색으로 보일 수 있다. 하지만 우린 모두 이걸 파란색이라고 부른다. 물질을 이루는 원자의 거의 전부는 빈 공간인데, 마치 꽉 차 있는 것처럼 보인다. 이 세상은 사실 3차원 공간으로 이루어진게 아닐 수도 있다. 눈과 뇌가 현실을 3차원처럼 보이게 우리를 속이고 있을 수 있다. 물질이 실제로 어떤 모습인지 알 수 없다. 시각뿐만 아니라 모든 감각이 이와 같다. 우리는 감각기관이 작동하는 방식으로 세상을 투영한다. 
0 notes
iamcreepyrabbit-blog · 6 years ago
Text
발명과 발견
바벨의 도서관이라는 것이 있다. 호르헤 루이스 보르헤스의 소설 제목인데, 이 소설의 배경이기도 하다. 도서관에 있는 모든 책은 각각 400페이지이고, 한 페이지에는 1600자의 글자가 들어있다. 모든 글자는 알파벳(a-z)과 공백( ), 콤마(,), 마침표(.)로만 구성되어 있다. 도서관에는 이 글자들로 조합가능한 모든 책이 나열되어있다. 대부분의 책은 의미없는 쓰레기고, 가치있는 책을 찾기 위해 사람들이 돌아다닌다.
누군가 도서관에서 ‘무한동력 배터리'의 제조법을 찾기 위해 평생을 바쳤다. 그는 수 많은 책을 살펴봤고 마침내 제조법이 적힌 책을 찾아내었다. 이 제조법을 아직 인류에게 알려지지 않은 것이다. 그는 발명을 한 것일까, 발견을 한 것일까?
이제 도서관 밖을 보자. 누군가 '무한동력 배터리'의 제조법을 찾기 위해 평생을 바쳤다. 그는 무수한 가능성을 열어둔 채 수 많은 실험을 했고 마침내 제조법을 찾아내었다. 이 제조법은 위의 제조법과 같은 것으로 역시 아직 인류에게 알려지지 않은 것이다. 그는 발명을 한 것일까, 발견을 한 것일까?
위의 물음과 아래의 물음에 대한 대답이 다르다면 발명과 발견의 차이는 무엇일까?
여러 가능성 중 무언가 만드는 방법을 찾는 것을 우리는 발명이라 불렀다. 그러나 우리 우주에 존재하는 원자의 종류와 개수는 유한하고, 물리법칙은 불변이다. 즉 무언가를 만들 수 있는 경우의 수는 유한하다. 도서관의 책들도 일정한 조건 하에 유한한 가능성의 집합이다. 이 책 더미 속에서 무언가 만드는 방법을 찾는 것을 발명이라고 할 수는 없을까?
도서관의 책과 마찬가지로 우리 우주 안에서 어떤 것이 존재할 수 있는지 없는지는 이미 결정되어있다. 우리가 배운게 틀리지 않았다면 현실에서 '무한동력 배터리'는 존재할 수 없다. 어떤 실험을 해도 만들 수 없고, 도서관에서 진짜 '무한동력 배터리 제조법'는 찾을 수 없다.
0 notes
iamcreepyrabbit-blog · 6 years ago
Text
말하는 재미
요즘 친구 H군과 함께 영어로 말하고 듣는 연습을 하고 있다.
영어를 잘하는 이 친구는 군대 때문에 한국에 잠시 왔다가 졸업하기 위해 올해 11월에 다시 미국으로 건너갈 예정이다. 나는 H군이 미국가기 전까지 내 영어 연습을 도와주면 합당한 금액을 주겠다고 제안했고, 그는 돈은 됐고 만날때마다 맛있는거나 사달라고 하면서 딜이 성사됐다. 일주일에 세 번정도 만나는데 맛있는 걸로만 먹으러 다니다보니 매번 밥값만으로도 큰 지출이 발생하고 있다. 그래도 시간을 내준 H군이 고마워서 돈은 전혀 아깝지 않다.
영어를 연습하려면 말을 많이 해야한다. 말을 많이 하기 위해서 H군과 만나기 전에 무엇을 얘기할지 주제를 몇 가지 골라서 간다. 내가 준비한 주제라서 내가 더 많이 알고 있고, 자연스레 더 많이 말을 할 수 있다. 대화는 주제만 딱 얘기하고 끝나지 않고 우리만 알고 있는 이야기나 공통의 다른 관심사로 자유롭게 흐른다.
H군과의 영어 대화는 내게 편하다. 내가 틀린 영어를 쓰지 않을지 걱정하지 않고 자연스레 일단 뱉어본다. 내가 어떤 개드립을 쳐도 이상하게 보이지 않을 걸 알기에 이런 저런 생각을 말로 표현한다. 어떤 단어를 써야할지 모르는 경우엔 듣는 사람이 답답하든 말든 신경쓰지 않고 그 단어의 개념을 풀어서 설명해보려 애쓴다. (이게 더 연습된다는 핑계로 단어는 전혀 외우지 않는다)
나는 말하기보단 듣기를 더 좋아해서 내가 재미있어하는 것들에 대한 이야기를 잘 하지 않는다. 그런데 영어 연습을 하면서 말하기와 친해지려 노력해보니 이게 듣는 것만큼 재미있다는 것을 알았다.
1 note · View note