<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Kay on the rails</title>
    <description>Kay as known as Hannal walks on the rails
</description>
    <link>http://blog.hannal.com/</link>
    <atom:link href="http://blog.hannal.com/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Sun, 12 Nov 2023 05:23:08 +0000</pubDate>
    <lastBuildDate>Sun, 12 Nov 2023 05:23:08 +0000</lastBuildDate>
    <generator>Jekyll v3.9.3</generator>
    
      <item>
        <title>맥을 Docker remote host로 사용하기</title>
        <description>&lt;h2 id=&quot;배경&quot;&gt;배경&lt;/h2&gt;

&lt;p&gt;작업 PC로 인텔 아이맥 27인치를 사용한다. 디스플레이가 괜찮은데다 램을 128기가로 넉넉하게 구성해서 유용하다. 그런데 CPU 발열이 심한 편이라서 CPU 과부하가 심할 땐 과열로 시스템이 뻗곤 한다. 그래서 작업 PC로 사용할 맥 스튜디오를 샀다. 가격이 부담스러워서 램 용량이 적은 32기가 메모리 모델을 샀는데, 램을 주로 점유하는 것들을 아이맥에서 띄워 서버용으로 사용할 계획이기 때문이다.&lt;/p&gt;

&lt;p&gt;램을 많이 사용하는 것 중 하나는 Docker이다. 그래서 아이맥을 Docker remote host용으로, 간단히 말해서 Docker 서버용으로 사용하도록 설정하기로 했다. 그런데 Docker를 서버로 구동할 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dockerd&lt;/code&gt;는 Linux에서 동작하며, Mac OS는 지원하지 않기 때문에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;virtualbox&lt;/code&gt;를 이용해 Linux를 가상화하여 Guest OS로 구동하여 Docker를 사용한다. 따라서 아이맥에 virtualbox로 Linux를 설치하고, 그 가상화로 구동하는 Linux에 구성한 dockerd를 사용해야 한다. Host OS인 Mac OS와 Guest OS인 Linux 간 네트워크도 연결해주고.&lt;/p&gt;

&lt;p&gt;귀찮기도 하고 웬지 무겁고 비효율적일 것 같은데, 이 글에서 사용할 Docker desktop 대체재인 &lt;a href=&quot;https://orbstack.dev&quot;&gt;OrbStack&lt;/a&gt;을 사용하여 쉽게 설정하고 가볍게 동작하는 환경을 구성할 수 있다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;아이맥 : Ventura 13.4&lt;/li&gt;
  &lt;li&gt;Docker client : OrbStack&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;docker-설치&quot;&gt;Docker 설치&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Docker remote host인 아이맥에 &lt;a href=&quot;https://docs.orbstack.dev/install&quot;&gt;OrbStack 설치&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;원격으로 접근할 client인 맥스튜디오에도 설치.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;docker-engine을-daemon으로-구동할-ubuntu-서버-구성&quot;&gt;Docker engine을 daemon으로 구동할 Ubuntu 서버 구성&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;아이맥에 OrbStack으로 Linux machine을 만든다.
    &lt;ul&gt;
      &lt;li&gt;참고 : &lt;a href=&quot;https://docs.orbstack.dev/machines/&quot;&gt;Using Linux machines&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;orb create &lt;span class=&quot;nt&quot;&gt;--arch&lt;/span&gt; amd64 ubuntu my-ubuntu
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;아이맥이 인텔맥이라서 아키텍쳐를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;amd64&lt;/code&gt;로 지정했다.&lt;/p&gt;

&lt;p&gt;Linux machine을 만들고 나면 ssh로 접근 가능하다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;orb &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; my-ubuntu &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; default
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;기본으로 생성되는 계정 이름이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;이다. 기본으로 존재하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt;로 접근해도 된다.&lt;/p&gt;

&lt;p&gt;Linux machine을 생성하면 OrbStack이 이 ubuntu에 ssh로 접근할 수 있게 ssh host configuration을 자동으로 만들어줘서 더 적게 타이핑하여 접근할 수 있다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh orb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orb&lt;/code&gt;가 OrbStack이 만든 ssh host configuration이다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# ~/.orbstack/ssh/config
# 이 파일은 ~/.ssh/config 에서 Include 하도록 OrbStack이 설정한다.

Host orb
  Hostname 127.0.0.1
  Port 32222
  # SSH user syntax:
  #   &amp;lt;container&amp;gt;@orb to connect to &amp;lt;container&amp;gt; as the default user (matching your macOS user)
  #   &amp;lt;user&amp;gt;@&amp;lt;container&amp;gt;@orb to connect to &amp;lt;container&amp;gt; as &amp;lt;user&amp;gt;
  # Examples:
  #   ubuntu@orb: container &quot;ubuntu&quot;, user matching your macOS user
  #   root@fedora@orb: container &quot;fedora&quot;, user &quot;root&quot;
  User default

  # replace or symlink ~/.orbstack/ssh/id_ed25519 file to change the key
  IdentityFile ~/.orbstack/ssh/id_ed25519

  # only use this key
  IdentitiesOnly yes
  ProxyCommand '/Applications/OrbStack.app/Contents/Frameworks/OrbStack Helper (VM).app/Contents/MacOS/OrbStack Helper (VM)' ssh-proxy-fdpass /Users/hannal
  ProxyUseFdpass yes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;docker-engine-설치&quot;&gt;Docker engine 설치&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Linux machine에 접근하여 docker engine을 설치한다.&lt;/li&gt;
  &lt;li&gt;참고 : &lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;Install Docker Engine on Ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;충돌날만한-불필요한-패키지-제거&quot;&gt;충돌날만한 불필요한 패키지 제거&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;pkg &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;docker.io docker-doc docker-compose podman-docker containerd runc&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get remove &lt;span class=&quot;nv&quot;&gt;$pkg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;docker-repository-설정&quot;&gt;Docker repository 설정&lt;/h3&gt;

&lt;p&gt;HTTPS로 repository에서 패키지를 가져와 설치해야 하므로 관련 패키지를 설치한다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ca-certificates curl gnupg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Docker의 공식 GPG 키를 추가한다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; 0755 &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; /etc/apt/keyrings
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;gpg &lt;span class=&quot;nt&quot;&gt;--dearmor&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.gpg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Docker repository를 등록한다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;deb [arch=&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;dpkg &lt;span class=&quot;nt&quot;&gt;--print-architecture&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
  &quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; /etc/os-release &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; stable&quot;&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class=&quot;c&quot;&gt;# repository를 새로 등록했으니 update 해준다.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Docker 공식 repository에서 패키지를 가져와 설치한다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;docker를-system-서비스로-등록한다&quot;&gt;Docker를 system 서비스로 등록한다&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;나는 systemd를 사용하므로 systemd 설정을 했다.&lt;/li&gt;
  &lt;li&gt;참고 : &lt;a href=&quot;https://docs.docker.com/config/daemon/remote-access/&quot;&gt;Configure remote access for Docker daemon&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;docker.service
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;containerd.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;remote-access되도록-docker-설정&quot;&gt;Remote access되도록 docker 설정&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/docker/daemon.json&lt;/code&gt;에 설정하거나 systemd에서 docker 서비스 실행에서 인자로 지정하거나. 여기에선 후자 방법으로 했다. daemon 설정이 더 늘어나면 그때 분리하려 한다.&lt;/p&gt;

&lt;p&gt;후자 방법은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/systemd/system/docker.service.d/override.conf&lt;/code&gt; 파일을 편집하는 것이다. 이런 &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units#editing-unit-files&quot;&gt;단위 구조는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edit&lt;/code&gt; 명령을 사용&lt;/a&gt;하면 구성된다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl edit docker.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이제 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/systemd/system/docker.service.d&lt;/code&gt; 디렉터리가 생기고 그 안에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;override.conf&lt;/code&gt; 파일이 만들어지는데, 그곳에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dockerd&lt;/code&gt; 실행 유닛을 작성한다.&lt;/p&gt;

&lt;div class=&quot;language-conf highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[&lt;span class=&quot;n&quot;&gt;Service&lt;/span&gt;]
&lt;span class=&quot;n&quot;&gt;ExecStart&lt;/span&gt;=
&lt;span class=&quot;n&quot;&gt;ExecStart&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;usr&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;dockerd&lt;/span&gt; -&lt;span class=&quot;n&quot;&gt;H&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;:// -&lt;span class=&quot;n&quot;&gt;H&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tcp&lt;/span&gt;://&lt;span class=&quot;m&quot;&gt;127&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;:&lt;span class=&quot;m&quot;&gt;2375&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-H&lt;/code&gt; 옵션은 Docker daemon 소켓이 연결할(사용할) 호스트를 지정한다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-H fd://&lt;/code&gt;는 Unix file descriptor로 호스트 연결을 하는 것이고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-H tcp://127.0.0.1:2375&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1:2375&lt;/code&gt; TCP IP로 연결하는 것이다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-H fd://&lt;/code&gt;는 기본(default) 옵션이고, TCP로 원격 접근하도록 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-H tcp://...&lt;/code&gt; 옵션을 추가한 것이다.&lt;/p&gt;

&lt;p&gt;systemd service 스크립트를 수정하지 않고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;override.conf&lt;/code&gt;를 수정하는 이유는 자동으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;override.conf&lt;/code&gt;가 포함되기 때문이다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo systemctl status docker.service&lt;/code&gt; 명령을 실행해 확인해보자.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl status docker.service

● docker.service - Docker Application Container Engine
     Loaded: loaded &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;/lib/systemd/system/docker.service&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; enabled&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; preset: enabled&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    Drop-In: /etc/systemd/system/docker.service.d
             └─http-proxy.conf, override.conf
     Active: active &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;running&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; since Sun 2023-08-13 17:54:09 KST&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 1h 24min ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 12127 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;dockerd&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
      Tasks: 20
     Memory: 24.6M
        CPU: 973ms
     CGroup: /system.slice/docker.service
             └─12127 /usr/bin/dockerd &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; fd:// &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; tcp://127.0.0.1:2375
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drop-In&lt;/code&gt;에 보면 include한 conf 파일 중 하나인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;override.conf&lt;/code&gt;가 명시되어 있다.&lt;/p&gt;

&lt;p&gt;systemd service script를 변경했으니 systemd가 해당 script를 다시 적재하도록 하고, docker를 재실행한다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl reload docker.service

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart docker.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;mac-os에-tcp-forwarding-설정&quot;&gt;Mac OS에 TCP forwarding 설정&lt;/h2&gt;

&lt;p&gt;Docker remote host에 Linux machine을 만들어 docker daemon을 띄웠으니 외부에서 이 host에 접근해 docker host에 접근하도록 TCP 포워딩을 한다. Linux machine에 하는 것이 아니라 Linux machine를 guest OS로 구동한 Host OS인 Mac OS에 설정하는 것이다. 내 경우는 아이맥에 하는 것이다.&lt;/p&gt;

&lt;h3 id=&quot;원격-로그인-설정&quot;&gt;원격 로그인 설정&lt;/h3&gt;

&lt;p&gt;[System Settings] - [General] - [Sharing]에 들어간다(한국어로 뭐라고 나오는지는 모르겠다). &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Remote Login&lt;/code&gt;(원격 로그인?)을 활성화한다. ssh daemon을 사용할지를 설정하는 것이다. 느낌표 아이콘을 클릭하면 ssh로 접근을 허용할 계정을 지정하는 화면이 나온다. 난 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Administrators&lt;/code&gt;는 빼고 내 개인 계정을 추가했다.&lt;/p&gt;

&lt;p&gt;다른 Host에서 ssh로 접근 가능한지 테스트 해본다. 난 맥 스튜디오에서 아이맥으로 접근하는 것이다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; ~/.ssh/id_rsa hannal@ip-address
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;잘 된다. 타이핑하기 귀찮으니 ssh host 설정을 해준다.&lt;/p&gt;

&lt;div class=&quot;language-conf highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# ~/.ssh/config 파일
&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Host&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imac&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;HostName&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt;주소
  &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hannal&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;IdentityFile&lt;/span&gt; ~/.&lt;span class=&quot;n&quot;&gt;ssh&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;id_rsa&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;IdentitiesOnly&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yes&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;PasswordAuthentication&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이제 호스트명인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imac&lt;/code&gt;으로 간편하게 접근된다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh imac
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이 ssh host인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imac&lt;/code&gt;은 ssh 호스트가 필요한 곳엔 어디에서든지 사용할 수 있으므로 설정해두면 편리하다.&lt;/p&gt;

&lt;h3 id=&quot;ssh-daemon에-tcp-forwarding-허용-설정&quot;&gt;SSH daemon에 TCP forwarding 허용 설정&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/ssh/sshd_config&lt;/code&gt; 파일을 열어 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AllowTcpForwarding&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GatewayPorts&lt;/code&gt; 설정을 추가하거나 주석을 해제한다.&lt;/p&gt;

&lt;div class=&quot;language-conf highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;AllowTcpForwarding&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yes&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;GatewayPorts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이런 설정을 하는 이유는 OrbStack은 보안을 이유로 OrbStack이 관리하는 Linux machine에 원격으로 접근하지 못하기 때문이다. OrbStack이 구동한 localhost에서만 접근 가능하다.&lt;/p&gt;

&lt;p&gt;ssh daemon 설정을 변경하면 ssh daemon에 반영되도록 재실행해야 한다. 앞서 [Sharing]에 있던 [Remote Login]을 비활성화했다가 다시 활성화하면 된다.&lt;/p&gt;

&lt;h2 id=&quot;client-설정&quot;&gt;Client 설정&lt;/h2&gt;

&lt;p&gt;Client, 즉, 램이 32기가 뿐인 맥 스튜디오에 있는 docker가 아이맥에 있는 docker를 서버로 사용하도록 지정하는 건 대개 두 가지 방법 중 하나로 한다.&lt;/p&gt;

&lt;h3 id=&quot;docker_host-환경-변수-사용&quot;&gt;DOCKER_HOST 환경 변수 사용&lt;/h3&gt;

&lt;p&gt;환경변수인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DOCKER_HOST&lt;/code&gt;에 docker가 사용할 host를 지정하는 방법이다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DOCKER_HOST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;imac
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose up

&lt;span class=&quot;c&quot;&gt;# 또는&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$ DOCKER_HOST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;imac docker-compose up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;하지만 환경변수는 실수하기 십상이니 다른 방법을 사용한다.&lt;/p&gt;

&lt;h3 id=&quot;docker-context-사용&quot;&gt;docker context 사용&lt;/h3&gt;

&lt;p&gt;docker에서 제공하는 context 기능을 이용하는 것이다. 먼저 Docker remote host인 imac에 대한 context를 만든다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker context create &lt;span class=&quot;nt&quot;&gt;--docker&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ssh://imac imac-docker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imac-docker&lt;/code&gt;는 context 이름이다. 잘 만들어졌는지 확인해본다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker context list

NAME            DESCRIPTION   DOCKER ENDPOINT       ...
default &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;       Current ...   unix:///var/run/docker.sock
imac-docker                   ssh://imac
orbstack        OrbStack      unix:///Users/hannal/.orbstack/ ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;가 기본으로 존재하고, OrbStack을 설치하면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orbstack&lt;/code&gt;도 생긴다. 이들을 포함하여 방금 만든 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imac-docker&lt;/code&gt; context가 목록에 나온다.&lt;/p&gt;

&lt;p&gt;docker context는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; 보조 명령어로 사용한다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker context use imac-docker

Current context is now &lt;span class=&quot;s2&quot;&gt;&quot;imac-docker&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이제 docker는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imac-docker&lt;/code&gt; context에 지정된 Host에 연결한다. 확인해보자. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imac-docker&lt;/code&gt; context를 지정한 상태에서 docker container를 구동한다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose up

&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;+] Running 4/0
 ✔ Container redis      Created                     0.0s
Attaching to redis
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;아이맥에 있는 docker에 컨테이너가 구동된 것이라면 맥 스튜디오에 있는 docker에는 구동된 컨테이너가 없을테니 그걸 확인한다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker context use default

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker ps

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;아무 컨테이너도 없다. 아이맥에 있는 docker를 확인해본다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh imac

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker ps

CONTAINER ID   IMAGE                ...
2b806a9e441e   redis:7              ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;잘 떠있다. 이렇게 5K 해상도 디스플레이가 달린 Docker 서버를 마련했다.&lt;/p&gt;

&lt;h2 id=&quot;뜻밖의-공지&quot;&gt;뜻밖의 공지&lt;/h2&gt;

&lt;p&gt;그나저나 오랜만에 블로그에 글 올리려고 jekyll을 실행하니 설치에서 시간 낭비했고, 동작도 느리다. hugo로 이전해보려 했는데, 깔끔하게 이전되지도 않는다.&lt;/p&gt;

&lt;p&gt;요즘엔 글을 안 쓰기도 하고, 관리하는 것도 은근히 신경 쓰이니 이 참에 개인 블로그를 닫아야겠다.&lt;/p&gt;

&lt;p&gt;2003년부터 운영해온 개인 블로그는 2023년 8월 13일자로 닫습니다. 그동안 한날의 개인 블로그에 와주신 분들께 감사 인사 드립니다.&lt;/p&gt;
</description>
        <pubDate>Sun, 13 Aug 2023 12:30:00 +0000</pubDate>
        <link>http://blog.hannal.com/2023/08/use-mac-as-docker-remote-host/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2023/08/use-mac-as-docker-remote-host/</guid>
        
        <category>docker</category>
        
        <category>mac</category>
        
        <category>아이맥</category>
        
        <category>imac</category>
        
        <category>docker remote host</category>
        
        <category>orbstack</category>
        
        
        <category>devlife</category>
        
      </item>
    
      <item>
        <title>2021년을 마치며</title>
        <description>&lt;p&gt;어째 이 블로그에 회고만 올리는 것 같다. 1쪽에 2년치 회고가 나란히 나열되는 게 민망하니 2022년엔 분발해야겠다.&lt;/p&gt;

&lt;h3 id=&quot;몸-건강&quot;&gt;몸 건강&lt;/h3&gt;

&lt;p&gt;작년부터 올해 상반기까진 몸을 치료하고 재활했다. 연초에 필라테스를 시작해서 일주일에 한두 번씩 다니고 있는데, 운동 수행 능력은 거의 안 는 것으로 보인다. 직업병은 다소 나아졌다.&lt;/p&gt;

&lt;p&gt;올해엔 백신을 여섯 방 맞았다. 코비드19 세 번, 독감 한 번, A형 간염 두 번. 이 정도면 우리 동네 백신대장 정도는 되겠지? 코비드19 1차 접종은 좀 힘들었지만 그래도 2~3일 감기몸살 앓는 정도로 지나갔고, 2차 접종은 감기 기운 도는 정도로 지나갔고, 3차 접종은 내 왼쪽 어깨에도 근육이 있다는 걸 인지하는 정도로 지나갔다. 생각해보면 어느 백신이든 난 부작용을 안 겪거나 거의 안 겪는 것 같다.&lt;/p&gt;

&lt;h3 id=&quot;정신-건강&quot;&gt;정신 건강&lt;/h3&gt;

&lt;p&gt;연초부터 개인사가 겹치며  제대로 정신이 털렸다. 그 시기엔 혼란스럽긴 해도 정서나 정신 체계에 영향을 받지 않고 있다고 여겼는데, 내게 문제가 일어나고 있다는 걸 2분기에 깨달았다. 두드러진 증상은 집중력 저하와 의욕 감소, 상황 과소 평가와 나 자신에 대한 과대 평가, 그리고 수면 장애였다.&lt;/p&gt;

&lt;p&gt;한 달 정도 쉬니 기분이 다소 나아졌지만, 집중력 저하와 수면 장애는 별 차이가 없었다. “아무리 봐도 ADHD인데…”라고 ADHD인 지인이 내게 한 말도 있고 해서 정신과에서 검사를 받았다. ADHD 의심 진단을 받고 약을(콘서타) 먹기 시작했는데, ADHD 사람들이 말하는 극적인 변화는 아쉽게도 경험하지 못했다. 차이가 있긴 있다.&lt;/p&gt;

&lt;p&gt;수면 장애는 좀 더 차이나게 약 효과를 봤다. 효과를 빠르게 봐서 수면제인 줄 알았는데 우울증 약이라고 한다. 평소 우울감을 모르며 지낸다. 정확히는 기복 없이 낮고 평평한 감정과 정서를 유지하며 산다. 하지만 우울감이 도드라지지 않은 것일 뿐 뇌에선 우울증이 일어나는 호르몬 변화가 있었나 보다. ADHD약은 조금씩 증량해서인지 소문과(?) 달리 천천히 변화를 체감하고, 우울증약은 빠르게 효과를 봤다. 과거형인 이유는 식욕 부진이 심해져 약을 바꿨는데, 식욕 부진은 해소됐지만 수면 질이 안좋아졌기 때문이다.&lt;/p&gt;

&lt;h3 id=&quot;취미&quot;&gt;취미&lt;/h3&gt;

&lt;p&gt;정신과에 가기 전에 정신 건강 회복을 위해 새로운 취미 활동을 시작했다. 실용음악학원에서 보컬과 드럼을 배우는데, 드럼은 좋아하는 악기여서 선택했다. 보컬 수업은 목 안 쉬고, 편안하게 말하면서도 전달력 있는 목소리로 대화하고 싶어서 등록했다. 스피치 학원을 가는 게 맞지만, 온라인에서 스피치 관련 강의 몇 개를 들어보니 나와 안 맞아서 출석을 제대로 안 할 것 같았다.&lt;/p&gt;

&lt;p&gt;드럼 수업은 재밌는데, 안 그래도 사지가 동기화 되어 움직여서 답답한데 악보 읽느라 박자 따라가기도 벅차다. 생소한 문법이나 패러다임인 프로그래밍 언어로 작성된 코드를 읽는 느낌이다. 음표를 봐야 하는데 음표 간격으로 박자를 세는, 눈이 본질이 아닌 것에 현혹되는 함정에 자꾸 빠진다. 그런 내 모습이 답답하고 재밌다.&lt;/p&gt;

&lt;p&gt;보컬 수업은 의지력을 꽤 발휘하며 출석하고 있다. 난 내 목소리가 탁성에 톤이 낮다고 생각했는데, 탁한 음색은 아니며 탁한 소리가 나는 건 발성이 좋지 않아서 그럴 거라고, 그리고 음역대가 남성 평균보다 조금 높다는, 생전 처음 듣는 피드백을 받았다. 내가 공감하지 못하는 걸 느꼈는지 바리톤 치고는 조금 더 높다는 것이지 고음 또는 초고음역대는 아니라고 부연해주었다.&lt;/p&gt;

&lt;h3 id=&quot;집중의-질&quot;&gt;집중의 질&lt;/h3&gt;

&lt;p&gt;ADHD약을 복용하면서 집중하는 질이 좋아졌다. 여전히 극적인 변화를 체험해보진 못했다. 과열된 CPU의 클럭을 낮춰 다소 느리게라도 동작하는 것처럼 내 두뇌는 더 느려진 것 같다. 초반엔 시간이 빨리가는 건 체감해서 집중력이 좋아져 몰입하니 시간이 빨리간 것인가 싶었는데, 좀 더 관찰해보니 아니었다. 엄청난 중력이 미치는 공간에 내가 들어서서 내 시간선이 느려진 느낌에 더 가깝다. 다른 시간선은 다를 바 없는 속도인데 내 시간이 느려져서 상대적으로 시간이 빨리 가는 것 같다. 내 사유체는 여전히 불쑥 끼어드는 다른 생각이나 사건(event)에 쉽게 붙잡혀 끌려간다. 그래서 초반엔 무능감이 커졌다.&lt;/p&gt;

&lt;p&gt;그럼에도 약효가 있다고 여기는 이유가 있다. 사유체가 어딘가로 끌려가는 상황을 예로 들면, 예전엔 순순히 투항한 것처럼 그대로 동행했다. 약 복용하면서부터는 즐겁게 따라가다 뿌리치고 다시 원래 맥락으로 돌아오는 데 성공하는 사건이 포착된다. 약 용량을 늘려가자 실은 동행이 아닌 납치라는 걸 또렷하게 인식하게 됐고, 납치되었다 되돌아오는 데 성공하는 빈도가 늘고 있다.&lt;/p&gt;

&lt;p&gt;여러 생각이 비슷한 영향력(가치, 우선순위)으로 머릿 속에서 날뛰고 시시각각 생기는데, 이 부분에도 변화가 있다. 양자요동 치듯 머릿 속에서 날뛰는 생각들이 다소 줄었다. 개체(생각) 수와 날뛰는 강도가 줄었으며, 무엇보다도 각 생각이 뿜어대는 영향력 차이를 가늠하는 게 점점 더 수월해진다. 온갖 생각이 시시각각 생기고 움직이는 현상(증상?)이 없어지진 않지만, 가장 영향력을 내는 생각을 찾아내고 그 놈을 놓치지 않은 채 따라가는 게 수월해져서 덜 혼란스럽다. 아니, 이전엔 누구나 다 그렇다고 마땅하게 여겨서 그런 상태를 혼란스럽다고 느낀 적이 없었다. 사람에 따라서는 그 상태를 혼란이나 혼돈이라고 느낀다면, 나 역시 그 느낌을 이젠 좀 알게 된 것 같다.&lt;/p&gt;

&lt;p&gt;ADHD가 아닌 사람은 평소에 머릿 속에 떠있는 생각 개수가 적거나, 많더라도 순번이나 영향력 같은 것이 있어서 정렬을 하며 사는 건가?&lt;/p&gt;

&lt;h3 id=&quot;42seoul&quot;&gt;42Seoul&lt;/h3&gt;

&lt;p&gt;42Seoul에 비상근 멘토로 참여해 여러 교육생분들을 멘토링했다. 탐나는 분을 여럿 만났다. 현업에서 뛰는 비상근 멘토가 멘토링하는 과정에서 교육생들 데려가도록 만드는 것이 비상근 멘토 제도의 목적 중 하나로 있는 게 분명하다.&lt;/p&gt;

&lt;p&gt;멘토링하다보니 단골처럼 몇 가지 주제가 반복되는 걸 알게 됐다. 2022년엔 블로그에 이 주제에 관해 글쓸 예정이다. 멘토링하며 나눈 이야기를 다루는 게 아니라 멘토링 단골주제 자체를 소재로 삼는 것이다.&lt;/p&gt;

&lt;p&gt;좋은 경험을 주는 멘토링은 뭔가 가르쳐주거나 도와야겠다는 의지가 충만한 채 멘토링하는 경우 보다는 멘티가 얘기 나누고 싶은 게 많아서 그가 이야기를 이끌어가며 이야기를 주고 받을 때, 그리고 멘토링 한 번으로 끝나지 않고 멘티와 피드백을 주고 받으며 멘토와 멘티 간 맥락이 유지되고 올라간 상태에서 진행한 멘토링이 좋은 경우가 많다.  예를 들어, 한 멘티는 이력서에 대해 두 번 멘토링을 했는데, 멘토링 과정에서 변화하는 각 버전 별 차이가 잘 구분이 되었다. 그 차이가 간접적인 피드백인 셈이다. 그래서 멘토링이나 코칭에 대한 여러 고민 주제 중 자연스럽게 피드백 루프를 형성하여 멘토링하는 내 만족도를 높이는 부분도 고민하고 있다.&lt;/p&gt;

&lt;h3 id=&quot;책&quot;&gt;책&lt;/h3&gt;

&lt;p&gt;3분기에 한 달 정도 쉬면서 방향성 없이 손에 잡히는대로 책을 읽었다. 상당히 효율 떨어지게 읽었다. 이해하고 배우며 읽는 게 아니라 다 읽는 데 목표를 두었다. 읽은 책을 다시 읽기도 했는데, 읽었다는 사실과 내용을 까먹어서 처음 보는 것 마냥 읽다가 예전에 책에 남긴 메모를 발견하는 식이다. 올해에 40권 정도 읽었는데, 3분기에 32권을 읽었다. 32권 중 20권은 400쪽이 넘는데, 대체로 기술서다. 책을 느리게 읽는 편인데도 이럴지니, 어쩌면 비지도학습으로 자연어 학습을 훈련하는 기계보다도 더 언어 활동을 안 했을지도 모른다.&lt;/p&gt;

&lt;p&gt;그렇게 대충 읽었지만, 그래도 몇 권 꼽아본다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;좋았던 책 : High output management&lt;/li&gt;
  &lt;li&gt;리더십, 코칭 : 유능한 관리자, 개발 7년차 매니저 1일차, 두려움 없는 조직, 코칭 리더십(존 휘트모어)&lt;/li&gt;
  &lt;li&gt;현실 도피를 도와준 책 : 죽음의 수용소에서, 블랙홀과 시간여행, 수학이 필요한 순간, 물리의 정석&lt;/li&gt;
  &lt;li&gt;재밌게 본 책 : 엘리너 올리펀트는 완전 괜찮아&lt;/li&gt;
  &lt;li&gt;기대 안 했는데 좋았던 책 : 제텔카스텐 - 글 쓰는 인간을 위한 두 번째 뇌&lt;/li&gt;
  &lt;li&gt;먹고 사는 데 도움을 준 책 : JavaScript로 함수형 프로그래밍 학습하는 책들, 전문가를 위한 파이썬, 프로덕트 리더십, 테스트 주도 개발로 배우는 객체 지향 설계와 실천, 돈의 심리학&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;아참, 3분기에 책을 읽으며 두 번 산 책 몇 권을 발견하는 성과도 있다.&lt;/p&gt;

&lt;h3 id=&quot;제텔카스텐&quot;&gt;제텔카스텐&lt;/h3&gt;

&lt;p&gt;얼마 전부터 메모하는 방법과 체계를 제텔카스텐(zettelkasten)에 맞춰가고 있다.&lt;/p&gt;

&lt;p&gt;제텔카스텐을 단순하게 소개하자면, 임시 메모를 기록하고, 그 메모를 자신의 언어로 즉 자신의 생각으로써 문헌 노트나 영구 노트로 정리한다. 문헌 노트나 영구 노트로 정리하고 나면 임시 노트는 제거한다. 문헌 노트와 영구 노트는 서로 연결하여 상향식으로 아이디어 군집(cluster)을 형성하는데, 연결하는 과정도 문헌 노트와 영구 노트를 작성하는 것처럼 자신의 생각(아이디어)이 기준이다. 제텔카스텐에 대한 메모이더라도 ADHD에 관한 내 생각이 메모와 노트를 작성한 이유라면, 그 메모엔 ADHD 관련 키워드를 달고 그 키워드와 관련된 기존 메모와 연결한다.&lt;/p&gt;

&lt;p&gt;지인 소개로 제텔카스텐을 처음 접할 땐 별 감흥이 일진 않았는데,  ADHD를 치료하는 최근에 비로소 관심이 생겨서 시도하고 있다. 한 달 정도 됐는데, 아직 성과는 없다. 이쯤되면 대개는 내게 안 맞다고 여기고 포기하는데, 내 취향에 맞을지도 모른다는 생각을 한 일이 있었다.&lt;/p&gt;

&lt;p&gt;난 임시 메모를 남발하고 있으므로 제텔카스텐을 제대로 실천하는 건 아니다. 생각보다 까다롭고 시간이 많이 들다보니 자꾸 미루고 있는데, 며칠 전부터 임시 메모 몇 개를 문헌 노트로 정리하기 시작했다. 책 구절은 기억 안 나는데다 문헌 노트로 정리한 임시 노트는 삭제하다보니 책을 읽으며 남긴 메모인데도 책에 대한 내용은 안 남고 책에 대한 내 생각이 남았다.&lt;/p&gt;

&lt;p&gt;글을 읽다보면 글을 쓴 글쓴이가 어떤 생각을 했는지, 어떤 사람인지 머릿 속에서 모델링이 이뤄진다. 아무 글이나 그렇진 않고 내 취향이나 성향에 맞으며 글쓴이의 생각이 드러나고 잘 쓴 글이어야 한다. 난 몇 달에 한 번씩 내 옛날 글 몇 개를 다시 읽는데, 내 글을 읽으면서 글쓴이, 그러니까 나에게서 독립/분리된 인격체로서 저 글을 쓴 글쓴이에 대한 모델링이 가끔 이뤄진다. 못쓴 글이지만 내가 쓴 글이니 가끔 그런 경험을 하는데, 바로 이 지점이다. 과거에 쓴 내 글로 인물을 모델링하는데, 그 인물이 나 혹은 나와 매우 비슷한 부류라고 느껴지는 인물이기에 사진이나 동영상 보다도 더 입체감 있게 과거 속 나를 보는 내 취향을 제텔카스텐 방법으로 남기고 꺼내보는 것이다.&lt;/p&gt;

&lt;p&gt;이 회고는 초고를 작성하는 데 40분, 퇴고하는 데 90분 걸렸다. 이 정도 시간을 들여서 쓴 글이 이 수준이라 마음이 아프다. 적어도 이 부분만큼은 제텔카스텐으로 개선할 수 있을 것 같다.&lt;/p&gt;

&lt;h3 id=&quot;2022년-계획&quot;&gt;2022년 계획&lt;/h3&gt;

&lt;p&gt;집필하고 있다. 2022년이면 8년차가 된다. 계약 당시 1.6 버전이던 Django는 2022년 중에 4.0 버전대로 접어든다. 음… 계약한 출판사에서는 왜 기다려주는 거지. ㅜㅜ 2022년엔 반드시 초고를 완료하겠다.&lt;/p&gt;

&lt;p&gt;몇 가지 주제로 블로그 글 혹은 적절한 매체나 방법으로 컨텐츠를 만들려 한다. 색다른 것도 있고, 아닌 것도 있다.&lt;/p&gt;

&lt;p&gt;팀을 만들고 있고, 내년엔 더 적극 집중과 관심을 투자할 계획이다.&lt;/p&gt;

&lt;p&gt;퇴고하며 거의 분량 반 정도를 쳐냈는데도 여전히 참 말 많다. 내가 말 많다는 걸, 심지어 갈수록 많아지는 걸 느낀다. 말 줄이는 노력도 계획에 넣어본다.&lt;/p&gt;

</description>
        <pubDate>Fri, 31 Dec 2021 13:00:00 +0000</pubDate>
        <link>http://blog.hannal.com/2021/12/closing/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2021/12/closing/</guid>
        
        <category>회고</category>
        
        
        <category>essay</category>
        
      </item>
    
      <item>
        <title>2020년을 마치며</title>
        <description>&lt;h3 id=&quot;탈진한-상반기&quot;&gt;탈진한 상반기&lt;/h3&gt;
&lt;p&gt;상반기는 탈진하도록 몸과 마음을 썼다. 2분기에 들어서는 시점에 탈진 징조를 느꼈고, 6월 중순부터 회복과 충전하는 시간을 가졌다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;내게 익숙하고 편한 몸무게로 감량&lt;/li&gt;
  &lt;li&gt;치과 치료&lt;/li&gt;
  &lt;li&gt;요통 치료&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;한창 크로스핏 할 땐 도무지 도달하지 못했던 몸무게인 67~68kg에 건강하지 못한 방식으로 도달하자 자전거 오래 타다 땅을 딛는 것처럼 몸이 불편했다. 그래서 1분기부터 부담 가지 않는 선에서 1일 1식 하며 체중 감량을 하여 약 4~6개월 만에 4~5kg를 빼고 유지하고 있다. 정형외과에서는 좀 더 경과를 보며 치료를 하되 운동을 재개해도 된다고 하여 내년엔 필라테스를 해볼 생각이다.&lt;/p&gt;

&lt;p&gt;마음도 어딘가 고장 난 것 같기도 하고 혹시 성인 ADHD인가 싶어 종합 심리 평가를 받아봤다. 종합 심리 평가에서 성인 ADHD 징후라도 언급해주길 기대했지만 그렇진 않아 괜히 했나 생각했는데, 결과 보고서를 보니 해보길 잘했다. 내가 모르던 강점이나 가능성이 나와서 흥미롭기도 했고, 익히 알고 있던 부분을 재확인하기도 했다. 현재 나의 상태(state)를 살펴본다는(inspect) 점에서 가끔 해볼 만 한 것 같다. 참고로 보고서에 나온 내 강점을 단순무식하게 곧이곧대로 받아들인다면,
나는야 작가 꿈나무.&lt;/p&gt;

&lt;h3 id=&quot;1일-1식&quot;&gt;1일 1식&lt;/h3&gt;
&lt;p&gt;몸무게 감량하려고 2월부터 1일 1식을 시작했다. 처음엔 상당히 힘들었지만 석 달쯤 되자 몸이 적응했다. 원래 아침을 안 먹기 때문에 아침 공복이 괴롭지 않는 편이고, 아주 엄격하게 1일 1식을 지키진 않아 예상보다 빨리 적응한 것 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;일주일에 하루는 먹고 싶은 대로 먹기&lt;/li&gt;
  &lt;li&gt;여행 가면 1일 1식 안 함&lt;/li&gt;
  &lt;li&gt;초기엔 저녁에 샐러드 위주로 먹고 1일 1식은 드문드문 함&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1일 1식에 몸이 맞춰지자 하루 시작이 가벼워서 좋다. 1일 2식을 할 땐 몰랐고 1일 1식을 유지할 때에도 몰랐는데, 저녁을 푸짐하게 먹거나 야식을 하면 다음 날 몸이 불쾌하게 무거운 걸 확연히 느꼈다. 조금 더 활력이 있긴 한데 크게 체감할 정도는 아니며 불쾌한 무게감은 더 크다. 하지만 하루 한 끼(점심)에 충분한 열량과 영양소를 섭취하지 못하면 다음 날 힘들긴 해서, 점심을 예전에 먹던 한 끼보다 1.2~1.5배 정도 더 먹고 잘 먹어야 한다.&lt;/p&gt;

&lt;p&gt;또 다른 변화는 커피와 술에 더 예민해진 것이다. 체력이 떨어져서 예민해졌다 생각했는데, 아무래도 1일 1식으로 체질이 변해서 그런 것 같다. 이제 커피는 하루에 한 잔으로 엄격하게 제한해야 하며, 맥주 500cc만 마셔도 술에 취해서 불편하다. 더 마시면 다음 날 머리가 아프고 땀에 흠뻑 젖은 내복을 입고 있는 것처럼 내 몸이 불편하다.&lt;/p&gt;

&lt;h3 id=&quot;계획과-다른-하반기-활동&quot;&gt;계획과 다른 하반기 활동&lt;/h3&gt;
&lt;p&gt;하반기 계획은 크게 세 가지 활동을 계획했다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;몸과 마음 회복/충전&lt;/li&gt;
  &lt;li&gt;목표 두지 않고 부담 없이 아무거나 공부&lt;/li&gt;
  &lt;li&gt;지출을 줄이고 느슨한 관계로 시간 많이 안 쓰는 일 하며 2020년 마무리하기&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;먹고 살아야 하니 3번을 꼼꼼하게 계획했다. 그래서 &lt;a href=&quot;https://innovationacademy.kr/&quot;&gt;이노베이션 아카데미&lt;/a&gt;에 비상근 개발 멘토로 지원했고, 미뤄둔 집필도 재개했다. 어딘가에 자문하거나 감수하기도 했다. 그런데 코비드19로 이노베이션 아카데미의 비상근 멘토 운영 계획에 차질이 생겨고, 이 활동에서 기대하는 수입은 사실상 사라졌다. 그래서 시간을 들여야 하는 일을 치렀다.&lt;/p&gt;

&lt;p&gt;한 프로젝트는 React를 공부하여 React와 React Native로 웹과 앱을 만드는 일이었다. React로 처음 개발하는 데다 Back-end도 포함해서 혼자 구현하다 보니 프로젝트 규모에 비해 시간을 많이 썼지만, 좋은 팀과 협업하여 스트레스 받지 않고 편하게 일했다.&lt;/p&gt;

&lt;p&gt;한 프로젝트는 Vue.js로 Front-end 개발하는 일이었다. 분명 시작은 Vue.js 개발이었는데, 어느 시점부터 Front-end이든 Back-end이든 문제를 해결하는 방향으로 일하는 방식이 바뀌었다. 그래서 Ruby on Rails 코드도 보게 됐는데, 내 성향이나 취향에 안 맞는 부분은 여전히 있지만 참 여러모로 예쁘고 우아한 언어와 도구라는 생각을 했다.&lt;/p&gt;

&lt;h3 id=&quot;모른다고-말하고-틀렸다고-인정하기&quot;&gt;모른다고 말하고 틀렸다고 인정하기&lt;/h3&gt;
&lt;p&gt;예전엔 몰랐는데, 아는 게 깊어질수록 그 분야에 모르는 게 있거나 내가 틀린 걸 인정하는 게 그리 부담스럽지 않아서 좋다. 깊이가 얕을수록 내가 모르는 걸 들키기 싫어하고 틀린 걸 어떡해서든 방어하려 했다. 좀 더 알게 될수록 아는 것만 말하는 게 불편하지 않다. 아는 것만 말해도 손해보지 않거나 불리해지지 않는 상황과 입장이 된 면도 있고.&lt;/p&gt;

&lt;p&gt;얕고 넓게 아는 걸 좋아하는 성향과 깊게 잘 알려는 태도 사이에서 아직은 흔들리는데, 깊게 파고드는 매력 중 하나를 이제나마 깨달아서 다행이고 지난 세월은 아쉽다.&lt;/p&gt;

&lt;h3 id=&quot;손그림&quot;&gt;손그림&lt;/h3&gt;
&lt;p&gt;꼬맹이가 미술에 관심과 집중력을 보여서 공통 취미나 관심사를 하나 정도 만들고자 손그림을 취미 생활 중 하나로 시작했다. 힘들고 어렵지만 재밌다. 내년엔 총 네 개 주제를 그리는 걸 목표로 삼았다.&lt;/p&gt;

&lt;iframe width=&quot;420&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/2XKOkWCAD5w&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

</description>
        <pubDate>Fri, 25 Dec 2020 13:00:00 +0000</pubDate>
        <link>http://blog.hannal.com/2020/12/closing/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2020/12/closing/</guid>
        
        <category>회고</category>
        
        
        <category>essay</category>
        
      </item>
    
      <item>
        <title>django-filter용 NULLS LAST 정렬 필터.</title>
        <description>&lt;p&gt;이 글 내용은 PostgreSQL을 기반으로 하며, 다른 RDBMS 에서는 확인 안 해봤다.&lt;/p&gt;

&lt;h3 id=&quot;django-orm으로-nulls-last-정렬&quot;&gt;Django ORM으로 NULLS LAST 정렬&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hannal&lt;/code&gt;이라는 모델이 있고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; 필드는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;을 허용한다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Hannal(models.Model):
    name = models.CharField(max_length=32, null=True, blank=True)
    birthday = models.DateField(null=True, blank=True)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;여러 데이터의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; 필드의 값이 다음과 같이 들어갔다고 가정하자.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'abc'&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'zyx'&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;None&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'lmn'&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이제 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; 필드로 오름차순(Ascending) 정렬해보자.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hannal.objects.order_by('name')
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;None&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'abc'&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'lmn'&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'zyx'&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;결과는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;이 먼저 나온다. 만약 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;이 아닌 데이터를 먼저 정렬하고 그 이후에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;인 데이터를 나열하려면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NULLS LAST&lt;/code&gt;로 정렬해야 한다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;from django.db.models import F

Hannal.objects.order_by(F('name').desc(nulls_last=True))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'abc'&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'lmn'&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'zyx'&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;None&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;우리가 원하는 대로 정렬됐다.&lt;/p&gt;

&lt;h3 id=&quot;django-filter의-orderingfilter&quot;&gt;django-filter의 OrderingFilter&lt;/h3&gt;

&lt;p&gt;이번엔 이를 &lt;a href=&quot;https://django-filter.readthedocs.io/&quot;&gt;django-filter&lt;/a&gt;에 적용해보자. django-filter에 정렬 필터인 &lt;a href=&quot;https://django-filter.readthedocs.io/en/stable/ref/filters.html#orderingfilter&quot;&gt;OrderingFilter&lt;/a&gt;를 사용하면 간편하게 정렬 필터를 적용할 수 있다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import django_filters as filters


class HannalFilter(filters.FilterSet):
    ordering = filters.OrderingFilter(
      fields=(
          ('name', 'name', ),
          ('birthday', 'saengil', ),
      ),
    )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;자세한 내용은 공식 문서에 나와있지만, 영문 싫어하는 사람도 많고 공식 문서가 썩 친절하진 않으니 설명하겠다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fields&lt;/code&gt;로 모델의 필드와 HTTP Query String에서 넘겨 받을 필드를 짝지어야 한다. 뒤(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;saengil&lt;/code&gt;)는 Query string으로 받을 값, 앞(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;birthday&lt;/code&gt;)은 모델의 필드/필드표현식을 뜻한다. 보통은 모델 필드명과 동일하게 하는 게 알아보기 좋지만, 다르게 해야 할 경우도 많다.&lt;/p&gt;

&lt;p&gt;우선 요청자(client)측에서 다른 이름을 쓰고 싶은 경우이다. Python 관례에 따르면 snake case 표기를 쓰겠지만, Front-end쪽에서는 camel case 표기를 대개 쓴다. 예를 들어 모델 필드명은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;birth_day&lt;/code&gt;인데 요청자는 굳이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;birthDay&lt;/code&gt;나 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;saengil&lt;/code&gt;을 쓰고 싶은 경우이다.&lt;/p&gt;

&lt;p&gt;다른 경우가 더 흔한 경우인데, 모델 관계(model relationship)를 필드 표현식으로 다뤄야 하는 경우이다. 예를 들어 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kay&lt;/code&gt;라는 모델이 있고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hannal&lt;/code&gt; 모델과 1:N 관계를 맺고 있다고 하자.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Kay(models.Model):
    name = models.CharField(max_length=32, null=True, blank=True)


class Hannal(models.Model):
    name = models.CharField(max_length=32, null=True, blank=True)
    birthday = models.DateField(null=True, blank=True)
    related = models.ForeignKey('Kay', on_delete=models.CASCADE)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그리고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kay&lt;/code&gt; 모델의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; 필드를 기준으로 정렬한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hannal&lt;/code&gt; 모델의 데이터를 가져오려면 다음과 같이 한다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hannal.objects.order_by('related__name')
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;여기서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;related__name&lt;/code&gt;을 django-filter 에서도 사용할 수 있다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class HannalFilter(filters.FilterSet):
    ordering = filters.OrderingFilter(
      fields=(
          ('name', 'name', ),
          ('birthday', 'saengil', ),
          ('related__name', 'kayname', ),
      ),
    )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이렇게 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fields&lt;/code&gt;를 지정하면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ordering&lt;/code&gt;키의 값으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;saengil&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kayname&lt;/code&gt;을 사용할 수 있다. &lt;a href=&quot;https://www.django-rest-framework.org/&quot;&gt;Django REST framework&lt;/a&gt;(이하 DRF)에 적용하면 URL을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?ordering=name&lt;/code&gt;로 하여 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; 필드에 대해 오름차순 정렬하거나 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?ordering=-name&lt;/code&gt;로 하여 내림차순 정렬한다. 이 뿐만 아니라 여러 개 필드를 정렬할 수도 있다. 예를 들어 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; 필드는 오름차순, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;birthday&lt;/code&gt; 필드는 내림차순으로 정렬하고자 한다면 HTTP Query String 으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?ordering=name,-birthday&lt;/code&gt;라고 하면 된다.&lt;/p&gt;

&lt;h3 id=&quot;orderingfilter에-nulls-last-적용&quot;&gt;OrderingFilter에 NULLS LAST 적용&lt;/h3&gt;

&lt;p&gt;편하다. 근데 django-filter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.4.0&lt;/code&gt; 버전 기준까지는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NULLS LAST&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NULLS FIRST&lt;/code&gt;를 지원하지 않는다. 그러므로 따로 구현해야 한다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OrderingFilter&lt;/code&gt;가 제공하는 기능은 그대로 쓰되 QuerySet 만들 때 null 정렬만 추가하면 되니 이 클래스를 상속받아 쓴다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;from django.db.models import F
import django_filters as filters


class NullsLastOrderingFilter(filters.OrderingFilter):
    def filter(self, qs, value):
        if not value:
            return qs
        for _v in value:
            is_desc = _v.startswith('-')
            field = self.param_map.get(_v[1:] if is_desc else _v)
            if not field:
                continue
            if is_desc:
                qs = qs.order_by(F(field).desc(nulls_last=True))
            else:
                qs = qs.order_by(F(field).asc(nulls_last=True))
        return qs


class HannalFilter(filters.FilterSet):
    ordering = NullsLastOrderingFilter(
      fields=(
          ('name', 'name', ),
          ('birthday', 'saengil', ),
          ('related__name', 'kayname', ),
      ),
    )

    class Meta:
        model = Hannal
        fields = ('name', 'birthday', )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;위에서 설명한 내용을 모두 반영한 코드이다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter()&lt;/code&gt; 메서드는 django-filter 가 넘겨받은 QuerySet 객체이다. DRF에 연동해 사용한다면 DRF가 이런 저런 조치를 취한 QuerySet일테고, 사용자가 만든 filter 들을 거친 QuerySet이기도 하다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NULLS FIRST&lt;/code&gt;를 적용하려면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nulls_last&lt;/code&gt; 대신 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nulls_first&lt;/code&gt;를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;True&lt;/code&gt;로 지정하면 된다.&lt;/p&gt;

</description>
        <pubDate>Fri, 13 Nov 2020 11:00:00 +0000</pubDate>
        <link>http://blog.hannal.com/2020/11/nulls-last-ordering-for-django-filter/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2020/11/nulls-last-ordering-for-django-filter/</guid>
        
        <category>django filter</category>
        
        <category>nulls last</category>
        
        <category>django</category>
        
        <category>postgresql</category>
        
        
        <category>devlife</category>
        
      </item>
    
      <item>
        <title>2019년을 마치며</title>
        <description>&lt;p&gt;2019년을 회고하는 글을 쓰려 했는데 2019년엔 블로그에 글을 하나도 안 썼더라. 2019년에 쓴 블로그 첫 글이 2019년 회고 글이라니 웬지 맥이 빠져서 회고 대신 간결하게 정리만 해본다.&lt;/p&gt;

&lt;h3 id=&quot;방송통신대학교-졸업&quot;&gt;방송통신대학교 졸업&lt;/h3&gt;

&lt;p&gt;2016년에 신입생으로 방송통신대학교(이하 방송대)에 입학했고, 얼마 전 마지막 학기의 기말시험을 마쳤다. 목표대로 4년 만에 졸업하여 기쁘다. 매 학기에 여섯 과목 중 한두 과목은 날로 먹었는데, 그래도 성적 중 70%를 차지하는 객관식 기말시험은 부담스러웠다. 성적이 아주 높진 않지만, 장학금도 반 정도 받았고 대학원에 응시할 정도는 달성했다. 물론 대학원엔 안 갈 거지만.&lt;/p&gt;

&lt;p&gt;방송대를 적극 추천한다. 나를 비롯해서 많은 사람이 자신의 의지력을 과대 평가하는데, 방송대는 자신의 의지력을 파악하는 아주 좋은 측정자이다. 커리큘럼과 교수진도 좋으며, MS Office, GitHub 등 학생 혜택도 받는다.&lt;/p&gt;

&lt;h3 id=&quot;프리랜서-생활을-마치다&quot;&gt;프리랜서 생활을 마치다&lt;/h3&gt;

&lt;p&gt;2016년 3분기부터 프리랜서 생활을 시작했다. 만 3년 했는데, 이렇게 오래 할 줄 몰랐다. 프리랜서 생활이 방송통신대학교 학업 진행에 딱히 더 유리하거나 불리한 건 없었다. 직장인에 비해 출석 수업에 나가기 편하긴 했지만, 늘 여러 일을 진행했기 때문에 일정하게 공부할 시간 자체를 확보하기 힘들었다.&lt;/p&gt;

&lt;p&gt;매년 직전 년도 기준으로 평균 30~40%씩 성장했던 건 고객 덕분이다. 유연하고 기민하게 협업하려는 목적으로 진행한 협업 방식이 중장기 프로젝트에서는 기대만큼 잘 동작하지 않았는데, 후반 프로젝트들은 중장기 계획으로 진행하여 나나 고객사들 모두 고생했다. 아쉬운 마음이 많이 남는다.&lt;/p&gt;

&lt;h3 id=&quot;회사-입사&quot;&gt;회사 입사&lt;/h3&gt;

&lt;p&gt;프리랜서 생활을 마치고 &lt;a href=&quot;http://fitpet.co.kr&quot;&gt;핏펫&lt;/a&gt;에 합류했다. 주변 사람들은 내가 1. 프리랜서 생활을 뜬금없이 그리고 갑자기 마치고선 회사에, 2. 그 회사의 사업 분야가 반려동물이라는 점, 3. 개발 실무자가 아니라 관리자로 합류했다는 사실을 놀라워하거나 신기해했다. 공감한다.&lt;/p&gt;

&lt;h3 id=&quot;1년에-3만km&quot;&gt;1년에 3만km&lt;/h3&gt;

&lt;p&gt;테슬라 모델X를 출고하고 만 1년 만에 주행거리 3만km를 돌파했다. 그동안 들어간 에너지 비용이 대략 40만원으로 1km 당 13~14원 정도 들었다. 이전에 타던 차가 디젤이고 리터 당 17~20km 정도 달렸는데, 리터 당 1,400원으로 계산하여 3만km를 달렸다면 대략 230만원 정도 연료비가 들었을 것이다.&lt;/p&gt;

&lt;p&gt;출고 1년 동안 여러 펌웨어 업데이트가 이뤄져 기능과 성능만 놓고 보면 1년 전에 산 그 차가 맞는 지 의심될 수준이다. 배터리는 1% 정도 열화되어 주행거리 20만km까지는 많이 불편하지 않을 것 같다.&lt;/p&gt;

&lt;h3 id=&quot;책&quot;&gt;책&lt;/h3&gt;

&lt;p&gt;한 해를 정신없이 보내서 책은 거의 안 읽었다고 생각했는데, 세보니 40권 정도 읽었다. 개발 관련 책은 필요한 부분만 보다보니 완독한 책은 몇 권 없다. 4분기엔 평일 두 시간씩 운전하다 보니 전자책을 TTS로 듣는다. 처음 읽는 책을 TTS로 들으니 개운하지 않다. 정독하기 전이나 후에 TTS로 듣는 게 나을 것 같다.&lt;/p&gt;

&lt;h3 id=&quot;개발-프로그래밍&quot;&gt;개발, 프로그래밍&lt;/h3&gt;

&lt;p&gt;알고리즘과 자료구조, 수학에 취약하다. 방송대 3학년부터 슬슬 압박을 받는 요인이었다. 공부하고 끝! 하는 게 아니라 그냥 평생 취미로 재미로 붙들고 살아야겠다.&lt;/p&gt;

&lt;p&gt;이런 저런 언어나 도구를 여럿 익혔지만, 별 감흥이 없다. 모래성 위에 깃발 꽂은들.&lt;/p&gt;

&lt;h3 id=&quot;영어&quot;&gt;영어&lt;/h3&gt;

&lt;p&gt;Safari Books online에서 낑낑대며 원서 읽고 있는데 번역서가 나온 적이 있다. 듣고 싶은 강의가 자막도 없이 영어로만 진행된 게 좀 있다. 영어가 발목을 잡은 적이 한두 번이 아니긴 했지만, 최근 유독 영어가 학습하는 데 제약이 되는 빈도가 늘었다. 의도적 수련으로 다시 공부하겠다.&lt;/p&gt;

&lt;h3 id=&quot;건강&quot;&gt;건강&lt;/h3&gt;

&lt;p&gt;중년에 들어섰다는 걸 절감한다. 손목, 어깨, 손가락, 허리 부근 직업병은 갈수록 심해지고, 체력 저하도 심각하다. 집중력과 의지력, 친절과 배려심은 체력에서 나오더라. 크로스핏을 사랑하지만 또 다치고 나서 잠정 중단했다. 체형 교정과 유연성을 개선하기 전엔 재개하면 안 될 것 같다.&lt;/p&gt;
</description>
        <pubDate>Tue, 31 Dec 2019 11:00:00 +0000</pubDate>
        <link>http://blog.hannal.com/2019/12/closing/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2019/12/closing/</guid>
        
        <category>회고</category>
        
        
        <category>essay</category>
        
      </item>
    
      <item>
        <title>테슬라 모델 X 구매기</title>
        <description>&lt;p&gt;얼마 전에 테슬라 모델 X를 샀다. 전기차 보조금을 받지 못하다보니 조금이라도 저렴하게 사려고 여러 방법을 모색했다. 돌이켜보면 복잡하거나 어려운 건 없으며 이미 여러 곳에 도움이 되는 정보가 많은데, 내 상황을 기준으로 구매 과정을 정리해본다.&lt;/p&gt;

&lt;p&gt;분명 이 글이 구매 과정에 도움이 될 터이니 구매한다면 내 테슬라 리퍼럴 코드를 사용해주시라. :)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://ts.la/kyeongmook93899&quot;&gt;https://ts.la/kyeongmook93899&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;구매-방법-별-테슬라-종류&quot;&gt;구매 방법 별 테슬라 종류&lt;/h2&gt;

&lt;p&gt;리스 등을 제외한 구매하는 방법에 따라 테슬라는 크게 세 가지 종류가 있다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;일반 차량&lt;/li&gt;
  &lt;li&gt;인벤토리 차량&lt;/li&gt;
  &lt;li&gt;테슬라코리아의 플릿카 중고판매 (이하 플릿카)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;일반 차량은 정식 경로(?)이자 일반화 된 구매 차량이다. 테슬라 홈페이지에서 차량 옵션을 지정하고 계약하면 공장에서 주문 내용에 맞는 차량을 생산한다. 장점은 옵션을 입맛대로 지정할 수 있고, 단점은 다른 두 종류에 비해 비싸고 출고하는 기간이 길다.&lt;/p&gt;

&lt;p&gt;인벤토리 차량은 여러 이유로 창고에 대기하는 &lt;em&gt;새 차&lt;/em&gt;이다. 그래서 계약하면 일반 차량에 비해 빠르게 출고 받으며, 경우에 따라서는 할인을 조금 해준다. 단점은 옵션이 적용된 완성 차량을 사는 것이므로 내가 원하는 옵션이 적용된 차량을 사지 못할 수 있다.&lt;/p&gt;

&lt;p&gt;플릿카는 법인 등이 한 번에 다수를 구매하는 플릿 판매를 뜻하는데, 전시, 시승 등 여러 용도로 테슬라 차량을 테슬라 코리아도 구매한다. 이런 차량을 판매하는 것이다. 중고차를 파는 것이므로 가격이 저렴한데, 차에 따라 주행 거리, 상태, 옵션이 다르므로 가격은 제각각이다.&lt;/p&gt;

&lt;p&gt;인벤토리 차량과 플릿카는 홈페이지 등에 나오지 않으므로 매장에 전화해서 재고 등을 직접 문의하면 된다.&lt;/p&gt;

&lt;h2 id=&quot;향상된-오토파일럿&quot;&gt;향상된 오토파일럿&lt;/h2&gt;

&lt;p&gt;구매할 때 “향상된 오토파일럿”은 꼭 추가하길 추천한다. 만약 이 옵션을 뺄 것이라면 다른 전기차를 사는 게 가격이나 충전 인프라 등을 고려해 더 낫다. 테슬라 차주 사이에서는 오토파일럿 옵션을 뺄 것이라면 차라리 차량을 빼고 오토파일럿을 사라는 우스개소리가 나올 정도로 테슬라의 오토파일럿(옵션명 : 향상된 오토파일럿)은 만족스럽다.&lt;/p&gt;

&lt;h2 id=&quot;선납금-오토캐시백&quot;&gt;선납금 오토캐시백&lt;/h2&gt;

&lt;p&gt;차량 구매에 들어가는 돈을 카드로 결제하면 일정 비율로 현금을 돌려주는 카드 상품으로 오토캐시백이 있다. 요즘은 1% 중후반에서 2% 초중반까지 돌려주는 것 같다.&lt;/p&gt;

&lt;p&gt;나는 일반 할부로 구매하므로 선납금에 대해 오토캐시백을 활용할 수 있었다. 선납금은 계약금을 포함하는데, 계약금을 먼저 납입한 뒤 출고할 때쯤 나머지 선납금 잔금을 납입한다. 그래서 오토캐시백도 두 번에 걸쳐 나눠서 진행하면 된다. 예를 들어 총 선납금을 3,000만원으로 잡으면 계약금 1,000만원에 대해 오토캐시백 적용하고, 잔금 2,000만원을 납입할 때 오토캐시백을 적용하면 된다.&lt;/p&gt;

&lt;p&gt;오토캐시백은 웹에서 검색하면 여러 업체가 나온다. 혹은 네이버 까페에서 오토캐시백으로 검색하면 활동이 왕성한 까페 몇 개가 나오는데, 그곳에서 상담 신청을 하면 연락이 온다. 주의할 점은 테슬라 코리아가 판매하는 차량에 대해 오토캐시백이 안 되는 카드사가 있다는 점이다. 오토캐시백 상담자에게 꼭 확인하자. 캐시백 비율은 카드사에서 직접 받는 것보다 중개/영업인(?)에게서 받는 것이 더 좋다. 예를 들어 하나카드 오토캐시백은 1% 초중반인데, 중개/영업 업체 끼고 하나카드 오토캐시백을 진행하면 1% 중반 이상 받는다.&lt;/p&gt;

&lt;p&gt;다음과 같이 오토캐시백을 받는 결제가 진행된다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;오토캐시백을 진행할 카드사 선택. 기존에 사용하는 카드로도 가능하나 이 경우 신규 고객 유치가 아니기 때문에 캐시백 비율이 줄어든다.&lt;/li&gt;
  &lt;li&gt;오토캐시백 받을 금액을 정함.&lt;/li&gt;
  &lt;li&gt;자신이 선택한 카드사로부터 가상계좌번호를 받으면 그곳에 계약금을 입금.&lt;/li&gt;
  &lt;li&gt;결제에 사용할 카드에 해당 금액에 대해 특별한도가 승인됨. 자신이 기존에 보유한 카드 한도가 계약금 보다 낮아도 무방하다. 예치한 계약금에 대해 특별한도 처리가 되기 때문이다. 이 금액은 테슬라 코리아쪽에 결제를 할 때 빠져 나간다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;선납금 잔금도 2~4번 과정을 그대로 반복한다. 잔금은 출고 방식에 따라 초기 견적 내용과 달라지므로 최종 잔금 안내를 받고 나서 잔금에 대한 오토캐시백을 진행하자.&lt;/p&gt;

&lt;p&gt;차량 계약금과 선납금은 전화 통화로 결제한다. 결제 금액을 분할할 수 있다. 나는 차량을 직접 등록하는 등 몇 가지 이유로 최종 잔금이 확정될 때까지 잔금 액수가 몇 번 변동되었다. 그래서 변동되지 않을 만한 단위와 나머지 자잘한 금액으로 분할해서 결제했다. 무슨 말이냐면 예를 들어 잔금이 2,XXX만원이 될 것으로 예상된다면 오토캐시백으로 2,000만원을 신청하고, 잔금을 결제할 때 테슬라 코리아에 2,000만원과 나머지 금액으로 나눠서 결제하겠다고 요청한 것이다. 오토캐시백을 받으려면 먼저 오토캐시백을 적용받을 금액을 예치해야 하고 이 과정이 몇 시간 걸릴 수도 있는데, 잔금이 정확하게 최종 확정되는 건 오토캐시백 준비를 마친 이후 단계이기 때문이다.&lt;/p&gt;

&lt;p&gt;당연한 얘기지만 차량 구매 금액에 대한 오토캐시백이다. 등록비나 부대 비용은 오토캐시백 대상이 아니다.&lt;/p&gt;

&lt;h2 id=&quot;차량-자가-등록&quot;&gt;차량 자가 등록&lt;/h2&gt;

&lt;p&gt;차량 등록을 직접 했다. 대행으로 진행하면 차량 번호를 못 고르며 대행비가 들지만, 직접 등록하면 자동차등록소에서 제시하는 후보군 중 하나를 고를 수 있고 대행비도 아낀다. 절차도 간단하다.&lt;/p&gt;

&lt;p&gt;먼저 테슬라 코리아측의 내 담당 OA에게 혹은 차량 출고 담당자에게 차량 등록을 직접 할 것이라고 알려준다. 차량 출고 담당자는 선납금을 모두 치를 때쯤 정해져 연락이 온다. 자가 등록을 한다고 하면 테슬라 코리아에서 필요한 서류를 등기로 보내준다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;리스 계약서 (리스 구매인 경우, 리스사에서 제공)&lt;/li&gt;
  &lt;li&gt;세금계산서&lt;/li&gt;
  &lt;li&gt;테슬라코리아 위임장&lt;/li&gt;
  &lt;li&gt;테슬라코리아 사용인감계&lt;/li&gt;
  &lt;li&gt;자동차 제작증&lt;/li&gt;
  &lt;li&gt;자동차 배출가스 인증서&lt;/li&gt;
  &lt;li&gt;자동차 소음인증서&lt;/li&gt;
  &lt;li&gt;자동차 제원표&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;구매자는 주민등록등본(법인은 법인등기부등본)과 자동차 보험증권 사본만 준비하면 된다.&lt;/p&gt;

&lt;p&gt;자동차 보험은 차대번호를 테슬라코리아로부터 받으면 가입할 수 있다. 주의할 점은 자동차보험 개시일이 자동차 등록일과 같거나 이전이어야 한다. 나는 자동차 등록을 하려는 날과 출고하는 날이 주말 제외하면 하루 차이라서 보험개시일을 출고일에 맞추는 실수를 했다. 당연히 자동차 등록은 거부되었고 부랴부랴 자동차보험을 해약하고 새로 가입했다.&lt;/p&gt;

&lt;p&gt;자동차등록은 아무 자동차등록소에서 해도 된다. 주민등록 상 거주지에 따라 공채 비용이 발생한다. (2019년 1월 16일 내용 보완)&lt;/p&gt;

&lt;p&gt;자동차등록소에 가서 자동차 신규등록 신청서를 작성하고 제출한다. 접수하는 데 2,000원, 인지료 3,000원 &lt;em&gt;현금&lt;/em&gt;이 필요하다. 임시 번호판은 필요 없다. 자동차 번호는 미리 정해진 후보들 중 하나를 선택하는 것이다. 모두 마음에 들지 않으면 다음 날 등록 신청 과정을 다시 진행해도 된다. 번호를 고르면 등록 신청이 접수된다. 다음은 징수과에 가서 세금(취등록세)을 낸다. 세금은 신용카드로 결제할 수 있다. 약 700만원 정도인데 2019년엔 감면 금액이 줄어들기 때문에 납부할 세금이 더 늘어난다. 징수과에서 징수를 마치고 확인을 받으면 차량 등록이 끝난다.&lt;/p&gt;

&lt;p&gt;차량 등록이 끝나면 선택한 차량 번호를 들고 번호판 제작과에 가서 번호판을 만든다. 일반 하얀 색 번호판과 전기차에 사용하는 파란 색 번호판이 가격이 다른데, 파란 색 번호판이 더 비싸다. 전기차이므로 반드시 파란 색 번호판을 만들 필요는 없어서 일부러 차량 외관 색에 어울리게 흰색 번호판을 고르는 경우도 있다. 번호판이 파란 색이어야만 친환경 차량 혜택을 받는 것이 아니며, 친환경 차량이라는 걸 알아보기 용이한 장점이 있다.&lt;/p&gt;

&lt;p&gt;…고 알고 있었는데, 잘못된 내용이다. 내 경우 흰색 번호판으로 만들었다가 찝찝해서 파란색 번호판으로 변경/제작했다. 알고보니 이는 행정 착오이며, 2017년 6월 9일 이후 출고된 전기차는 파란색 번호판 장착이 의무이다. (2019년 1월 16일 내용 보완)&lt;/p&gt;

&lt;p&gt;번호판은 긴 것으로 만들어야 한다. 번호판과 나사, 저공해 차량 스티커를 받으면 모든 과정이 끝난다. 번호판 가드(가이드)는 테슬라에서 제공하므로 받지 않아도 되며, 당연히 번호판도 테슬라 코리아에서 달아준다.&lt;/p&gt;

&lt;h2 id=&quot;자동차-보험&quot;&gt;자동차 보험&lt;/h2&gt;

&lt;p&gt;자동차 보험은 사람 마다 조건이 천차만별이므로 자동차보험 견적 업체를 이용해 비교 견적을 받으면 된다. 내 경우 가장 비싼 견적과 가장 저렴한 견적의 금액이 약 20만원 가까이 차이났다. 적용할 수 있는 할인 항목이 있으니 보험사 별로 견적을 받을 때 해당 항목으로 할인이 얼마나 되는 지 확인한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;차선유지장치&lt;/li&gt;
  &lt;li&gt;긴급제동장치 (전방충돌방지)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;테슬라 차량 펌웨어 v9에는 블랙박스 기능이 추가됐지만, 이걸로 블랙박스 할인이 가능한지는 모르겠다. 차선유지장치는 현재(2018년 12월) 기준으로 대부분 보험사에 할인 항목으로 있는 것으로 보이고, 긴급제동장치는 일부 보험사에서만 할인해주는 것 같다. 해당 기능은 차량에 부가 설치한 것이 아닌 기본 탑재라고 대답하면 된다. 나는 블랙박스, 만 6세, 차선유지장치, 대중교통이용에 대해 할인을 받아 20% 가까이 할인 받았다. 또 보험사에 따라서는 특정 결제 방법에 대해 캐시백을 해주는데, 난 이 부분에서도 3만원을 캐시백 받았다.&lt;/p&gt;

&lt;p&gt;보험 가입할 때 블랙박스 장착 증빙, 차선유지장치 증빙을 할 수 없으므로 할인 항목에서 제외한다. 출고 후 전화를 걸어 증빙자료인 사진을 보내서 할인 받을 금액을 환급 받는다.&lt;/p&gt;

&lt;p&gt;자동차 보험 가입 담보 중 “자기신체손해”와 “자동차 손해”를 선택하는 부분이 있는데, 자동차 상해가 보험비는 좀 더 높지만 조건이 좋다. 자기신체손해는 진단에 따른 부상 급수에 따라 보험금이 지급되고 자동차 상해는 실비 100%를 지급하기 때문이다. 그런데 내가 가입한 보험사에서는 다른 실손보험과 중복 보상을 하지 않는다. 이미 나는 실손보험에 가입했기 때문에 고민 끝에 자기신체손해를 선택했다. 이 부분은 보험사와 상담해보길 권한다.&lt;/p&gt;

&lt;h2 id=&quot;전기차에-유용한-결제-카드와-전기차-충전-카드&quot;&gt;전기차에 유용한 결제 카드와 전기차 충전 카드&lt;/h2&gt;

&lt;p&gt;결제에는 크게 두 가지 카드를 많이 사용한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;BC그린카드&lt;/li&gt;
  &lt;li&gt;신한EV카드&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 부분에 대한 자세한 내용은 &lt;a href=&quot;https://cafe.naver.com/teslaownersclub&quot;&gt;한국 테슬라 오너스 클럽&lt;/a&gt;에 잘 정리된 글, &lt;a href=&quot;https://cafe.naver.com/teslaownersclub/2274&quot;&gt;충전 카드와 할인용 신용 카드에 대한 이해
충전 카드와 할인용 신용 카드에 대한 이해&lt;/a&gt;를 참고하자. 이 글에서는 내 상황에 맞춘 내용만 정리하자면,&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;거주지 주차장에 공용 완속 충전기(파워큐브)가 있어서 이 충전 시설 이용하는 데 신한EV 카드 사용&lt;/li&gt;
  &lt;li&gt;이외 공용 충전 서비스에 결제 카드로 BC그린카드 사용&lt;/li&gt;
  &lt;li&gt;하이패스 결제 카드로 신한EV 카드 사용&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;신한EV카드의 경우 신한EV카드 + 신한하이패스로 신청해야 한다. 두 카드를 따로 따로 신청해도 되지만 번거롭다.&lt;/p&gt;

&lt;p&gt;BC그린카드는 BC카드에서 발행하므로 입맛에 맞는 금융권 카드사의 카드로 신청하면 된다. 나는 연회비가 없는 하나BC그린카드 v1 으로 신청했다. 주의할 점은 이 카드는 하나카드 모바일 앱에서는 검색이 안 되므로 웹에서 검색해서 주문하면 된다.&lt;/p&gt;

&lt;p&gt;충전용 카드는 환경부에서 발급하는 카드와 한국전기차충전서비스(줄여서 한전충)에서 발급하는 해피차저 카드를 주로 사용한다. 전기차 충전 업체가 다양하므로 자신의 환경과 상황에 맞는 카드를 발급 받으면 되는데, 환경부 카드와 한전충 카드로 대부분 불편을 겪지 않고 소화할 수 있다.&lt;/p&gt;

&lt;p&gt;해피차저 카드와 환경부 카드 모두 차량 등록하기 전, 그러니까 차량 번호를 발급 받기 전에 해당 카드를 발급 받을 수 있다. 해피차저 카드는 아무 차량 번호로 카드를 먼저 발급 받고, 이후에 차량 번호가 나오면 기존에 입력한 차량 번호를 실제 차량 번호로 수정하면 된다. 환경부 카드의 경우 웹페이지에서 차량 번호를 변경할 수 없는데, 전화(1661-9408)해서 변경해달라고 하면 된다. 카드를 신청하면 실물 카드가 우편으로 배송되는데, 실물 카드 수령 전에 카드번호가 먼저 나온다.&lt;/p&gt;

&lt;h2 id=&quot;하이패스&quot;&gt;하이패스&lt;/h2&gt;

&lt;p&gt;전기차 할인이 되는 하이패스는 차량 등록을 해야 구매할 수 있다. 주로 AP 500과 AP 700 제품을 사용하는 것 같은데, 난 하이패스 카드를 언제든 꼈다 뺐다 할 수 있는 AP 500을 샀다. 정리하면 나는 AP 500 기기에 신한 하이패스 카드를 꽂으며, 후불형인 이 하이패스 카드에 신한EV 카드를 연동하였다. 이렇게 조합하면 톨게이트 비용 50%를 캐시백 받는데, 전기차는 50% 할인을 받으므로 결국 톨게이트 비용이 0원인 셈이다. 아쉬운 점은 이 프로모션이 2019년 2월에 종료된다는 점인데, 소문에 따르면 기간이 연장될 것을 기대하고 있는 분위기이다.&lt;/p&gt;

&lt;p&gt;민자 고속도로의 경우 전기차 할인이 안 되는 곳이 있다(많이 혹은 대부분).&lt;/p&gt;

&lt;h2 id=&quot;충전-어댑터&quot;&gt;충전 어댑터&lt;/h2&gt;

&lt;p&gt;일부 플릿카를 제외하면 요즘 구매하는 차량은 거의 모두 북미형 충전 포트이다. 테슬라 독자 포트이므로 테슬라에서 운영하는 충전소 외엔 충전하지 못하는데, 국내 충전 시설을 이용하려면 충전기와 테슬라를 연결해주는 어댑터를 따로 마련해야 한다. 이 어댑터를 테슬라 코리아에서 판매할 수 없는 상황이어서 차량 구매 시 어댑터 가격을 빼주는데, 테슬라 온라인 스토어 등에서 직접 구매해야 한다.&lt;/p&gt;

&lt;p&gt;급속 충전 어댑터는 차데모를 사야 한다. 문제는 2018년 4월에 공표된 우리나라의 표준 충전 방식에 차데모 어댑터를 사용할 수 없으며, 차데모 어댑터를 사용할 수 있던 기존 충전기도 점차 줄어든다는 점이다. 즉 차데모 어댑터는 시한부이다. 꽤 비싸기 때문에 구매 여부를 잘 생각해야 한다. 나는 고민 끝에 안 샀다.&lt;/p&gt;

&lt;p&gt;완속 충전은 J1772 어댑터를 사면 된다. 차데모와 마찬가지로 테슬라 온라인 스토어에서 판매한다.&lt;/p&gt;

&lt;p&gt;테슬라 온라인 스토어는 테슬라 웹페이지에서 국가를 북미로 선택하면 접근할 수 있다. 직구는 되지 않으므로 배송 대행 서비스를 이용해야 한다. 배송은 매우 느려서 테슬라 온라인 스토어에서 배송 대행지까지 물품이 도착하는 데 10~14일 정도 소요된다. 나는 블랙 프라이데이 등 연말 상황이라서 배송 대행지에서도 7일 넘게 시간이 소요됐다. 차데모나 J1772 어댑터가 필요하다면 넉넉잡아 출고 전에 한 달 정도 시간을 염두에 두고 일찌감치 주문하길 추천한다.&lt;/p&gt;

&lt;h2 id=&quot;블랙박스&quot;&gt;블랙박스&lt;/h2&gt;

&lt;p&gt;차체가 워낙 커서 주차 상태에서 테러 당할 여지가 있다. 모델 X를 주차면이 좁은 곳에 세우면 주차면을 거의 꽉 채우고도 앞으로 머리가 빼꼼 나와서 문콕이나 범퍼 충돌을 당하는 경우가 많아 보인다. 그래서 블랙박스를 주차 중에도 동작하는 상시전원으로 사용하고자 했다. 상시 전원으로 블랙박스를 사용하려면 OBD 2에 연결하거나 보조 배터리를 사용하는데, 나는 고민 끝에 보조 배터리를 추가하여 블랙박스를 상시로 켜둔다. 테슬라의 12V 배터리는 여느 내연차 보다 용량이 작다고 한다. 그래서 자칫 12V 배터리를 과용하다 방전시키는 경우가 있다고 한다. 거대한 배터리를 품고 있는 전기차인데 정작 상시전원으로 12V 배터리를 사용하는 건 내연차 보다 더 제약을 받을 수 있다니 모순되게 느껴진다.&lt;/p&gt;

&lt;p&gt;OBD 2에 연결하여 사용하는 데 성공한 차주에 따르면 아직 별 문제는 없다고 한다.&lt;/p&gt;

&lt;h2 id=&quot;커뮤니티&quot;&gt;커뮤니티&lt;/h2&gt;

&lt;p&gt;테슬라 관련 커뮤니티가 몇 곳 있는데, &lt;a href=&quot;https://cafe.naver.com/teslaownersclub&quot;&gt;한국 테슬라 오너스 클럽&lt;/a&gt;은 기본으로 가입하길 추천한다. 이름처럼 꼭 테슬라 차주가 아니더라도 가입하고 활동할 수 있으며, 유용한 정보가 많다.&lt;/p&gt;

&lt;h2 id=&quot;출고&quot;&gt;출고&lt;/h2&gt;

&lt;p&gt;미리 잡은 출고 일시에 출고지(강서 서비스센터)로 가면 된다. 신분증과 차량 등록증을 가져가면 되며, 차량을 직접 등록했다면 번호판도 가져간다. 강서 서비스센터 인근에 딱히 식사할 만한 곳이 많진 않으니 출고 전후에 식사를 할 것이라면 시간 여유를 둘 필요가 있다.&lt;/p&gt;

&lt;p&gt;테슬라 차량의 마감은 악명이 높다. 단차나 도장 등 문제를 꼼꼼히 살펴보자. 인도했다고 해서 모르쇠로 대응하지 않으며, 언제든 친절하게 단차 등 마감 문제에 대해 대응하고 해결해준다고 한다. 테슬라 키팝(간단히 말해서 차 열쇠) 두 개, 모바일 충전 커넥터, 월 커넥터, 인도 확인증 받으면 출고는 끝난다. 모델 X 7인승엔 작은 차량용 소화기가 제공되는데, 국민권익위원회에 따르면 &lt;a href=&quot;https://www.acrc.go.kr/acrc/board.do?command=searchDetail&amp;amp;menuId=050505&amp;amp;method=searchDetailViewInc&amp;amp;boardNum=71229&amp;amp;currPageNo=1&amp;amp;confId=4&amp;amp;conConfId=4&amp;amp;conTabId=0&amp;amp;conSearchCol=BOARD_TITLE&amp;amp;conSearchSort=A.BOARD_REG_DATE+DESC%2C+BOARD_NUM+DESC&quot;&gt;2019년 하반기부터 모든 차량에 의무 설치하도록 한다&lt;/a&gt;고 한다.&lt;/p&gt;

</description>
        <pubDate>Sat, 29 Dec 2018 02:00:00 +0000</pubDate>
        <link>http://blog.hannal.com/2018/12/newbies-buying-guide-for-tesla-model-x/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2018/12/newbies-buying-guide-for-tesla-model-x/</guid>
        
        <category>테슬라</category>
        
        <category>모델X</category>
        
        <category>구매기</category>
        
        
        <category>essay</category>
        
      </item>
    
      <item>
        <title>생각 출력거름망</title>
        <description>&lt;p&gt;말을 하거나 글을 쓰는 게 갈수록 힘에 부친다. 출력거름망이 늘어가기 때문이다. 사소하게는 오타 확인부터 시작해 맞춤법과 단어 뜻을 확인한다. 글이나 말 내용과 무관하지만 나와 조금이라도 연관된 이해관계자를 2008년부터 의식하고, 몇 년 전부터는 아직 몸과 머리에 익지 않은 정치적 올바름이나 꼰대성억제 거름망도 덧붙였다.&lt;/p&gt;

&lt;p&gt;말 한 마디, 문장 하나 쓰는 데 이렇게까지 해야 하나 생각하기도 했다. 그런데 그렇게까지 해서 굳이 생각을 출력하지 않아도 괜찮더라. 아니다. 괜찮은 정도가 아니라 더 낫다. 그렇게까지 하지 않은 말이나 글을 남발했다. 내 말과 글 대다수는 세상에 그렇게까지 가치있는 영향을 끼치지 않는다. 그래서 출력하는 것과 출력하지 않는 차이가 크지 않다. 질과 양을 꾸준하고 일정하게 유지한다면 의미있게 차이나겠지만, 거름망 여러 겹을 거치는 의지력이 너무 많이 든다.&lt;/p&gt;

&lt;p&gt;내가 말하거나 글로 쓰려는 생각을 표현하는 사람이 많다. 내 취향에 안 맞고 미묘하게 내 생각과는 다르곤 하지만, 그 차이 때문에 굳이 비싼 의지력을 들일 필요는 없다. 예전엔 들였는데 이제는 의지력이 달려서 아껴 써야 한다.&lt;/p&gt;

&lt;p&gt;내가 바라는 해결책이 아니긴 하다. 여러 겹으로 작용하는 거름망에 익숙해져 거름망 모두를 한두 겹 거름망에 들어가는 의지력만으로도 통과하며 생각을 표현하고 싶다. 그런데 각 출력거름망은 고정되지 않아서 꾸준히 들여다보고 갱신해야 한다. 거름망을 통과하는 것도 벅찬데, 거름망 관리도 해야 한다.&lt;/p&gt;

&lt;p&gt;개인 홈페이지부터 시작해 20년 넘게 유지한 개인 공간을 앞으로 어떡할 지 고민해본다.&lt;/p&gt;
</description>
        <pubDate>Mon, 05 Feb 2018 02:00:00 +0000</pubDate>
        <link>http://blog.hannal.com/2018/02/the-filters/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2018/02/the-filters/</guid>
        
        <category>생각표현</category>
        
        <category>블로그</category>
        
        <category>정치적 올바름</category>
        
        <category>political correctness</category>
        
        
        <category>essay</category>
        
      </item>
    
      <item>
        <title>소울부스터 개발 후기</title>
        <description>&lt;p&gt;최근에 1년 동안 여성 맞춤 속옷을 제조하고 판매하는 소울부스터와 함께 일했다. 지난 1년을 돌아보는 후기를 적어본다. 참고로 기술 얘기 보다는 개발 과정(production)을 이야기하는, 말 그대로 개발 후기이다.&lt;/p&gt;

&lt;p&gt;이 글은 글자가 많다. 다 읽기 귀찮으면 “세 줄 요약”과 “마무리”만 봐도 된다.&lt;/p&gt;

&lt;h2 id=&quot;세-줄-요약&quot;&gt;세 줄 요약&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;소울부스터에 임시 개발 총괄자로 참여하여 프로젝트를 개시하고, 출시하여 운영했다.&lt;/li&gt;
  &lt;li&gt;함께 개발팀도 구축했다.&lt;/li&gt;
  &lt;li&gt;소울부스터는 연 이륙하기 시작했고 개발자를 충원하고 있다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;1-첫-만남&quot;&gt;1. 첫 만남&lt;/h2&gt;

&lt;p&gt;2년 다닌 회사를 퇴사한 지 얼마 안 되어 지인 소개로 &lt;a href=&quot;https://www.soulbooster.co.kr&quot;&gt;소울부스터&lt;/a&gt;를 만났다. 여성 맞춤 속옷을 직접 제조하고 판매하는 사업으로 창업한 지 얼마 안 된 시기였고, 온라인 서비스를 직접 개발하기 위해 소프트웨어 개발팀(이하 개발팀) 구축을 모색하고 있었다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.soulbooster.co.kr&quot;&gt;&lt;img src=&quot;/assets/uploads/2018/01/soulbooster-image.png&quot; alt=&quot;사진 출처 : 소울부스터&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;https://www.soulbooster.co.kr&quot;&gt;사진 출처 : 소울부스터&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;많은 회사가 소프트웨어 개발자(이하 개발자)를 뽑기 힘들고 어렵다며 아우성이다. 그들 말엔 온도 차가 있긴 하다만, 어쨌든 인력 시장이라는 측면에서 수요자는 다양해지고, 수는 늘고, 수요자의 사업 환경은 급변하여 수요는 빠르게 늘고 있는 데 반해 아직 공급은 수요를 못따라간다. 특히 괜찮은(?) 개발자 공급은 더욱 더 그렇다.&lt;/p&gt;

&lt;p&gt;난 자체 개발하지 말고 쇼핑몰 제품을 구입하거나 쇼핑몰 호스팅 서비스로 먼저 서비스를 출시하는 걸 조심스레 권했다. 대개는 잘 만든 솔루션을 쓰는 것이 낫다. 일정이 변수가 아닌 상수에 가깝게 명확해져서 장사라는 쇼핑몰의 사업 본질을 실행하고 가설을 검증하는 데 필요한 기간이 짧고 예측 가능하기 때문이다. 물론 입맛에 딱 맞지 않은 부분도 있겠지만, 대개 그런 부분은 사업 운용에 핵심이 아닌 경우가 많다. 핵심은 팔려는 제품이 고객에게 가치를 전달하는 것이다.&lt;/p&gt;

&lt;p&gt;소울부스터 박수영 대표는 소울부스터가 제공하는 가치는 고객 마다 다른 체형을 진단하여 몸에 잘 맞는 속옷을 제안하는 것이라 했다. 체형 진단을 위한 퀴즈 알고리즘을 중요한 사용자 경험으로 제공하고, 그러한 고객 경험을 서비스 시작부터 내재화해야 하기 때문에 개발팀을 초기부터 구축해야 한다고 했다. 동의했다.&lt;/p&gt;

&lt;p&gt;합류 논의는 서로 신중했다. 난 소울부스터가 추구하는 가치와 그 가치에 접근하는 방법에 공감했지만, 내가 잘 모르는 사업 아이템이어서 목표에 이르는 과정이 잘 그려지지 않아 조심스러웠다. 박수영 대표 역시 첫 개발자 영입을 신중하고 조심스러운 입장으로 진행하고 있었다. 서로 조심스러웠지만, 박수영 대표는 마냥 개발을 미룰 순 없다고 생각했는지 실행 방법을 논의하자고 했다. 우리는  고심한 끝에 실행 계획을 수립했다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;체형 진단 퀴즈 서비스와 쇼핑몰 개발을 내가 총괄하여 목표 일정에 맞춰 출시한다.&lt;/li&gt;
  &lt;li&gt;소울부스터는 개발자 구인을 계속 진행한다.&lt;/li&gt;
  &lt;li&gt;우리의 목표는 소울부스터에 개발팀이 구축되고 연이륙하는 것이다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;내가 정식으로 합류할 지 여부는 프로토타입 버전을 개발한 뒤에 다시 논의하기로 했다. 그렇게 우리의 협업은 시작되었다.&lt;/p&gt;

&lt;h2 id=&quot;2-소울부스터-개발-과정&quot;&gt;2. 소울부스터 개발 과정&lt;/h2&gt;
&lt;p&gt;구현에 사용할 도구(기술 스택)를 선택하는 중요한 기준은 개발 공정과 개발팀 두 가지를 구축한다는 목표에 근간을 두었다.&lt;/p&gt;

&lt;h3 id=&quot;back-end&quot;&gt;Back-end&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Application Server : Python 3, Django&lt;/li&gt;
  &lt;li&gt;DBMS, Storage : MySQL 5.7, Redis&lt;/li&gt;
  &lt;li&gt;OS : Ubuntu 16 LTS&lt;/li&gt;
  &lt;li&gt;Infrastructure : Amazon Web Service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;소울부스터는 나와 처음 만나기 전에 체형을 진단하는 퀴즈 알고리즘을 이미 상당히 설계한 상태였다. 데이터 관계와 논리 체계가 상당히 정형화 되어 있었는데, 향후 더 확장해서 활용할 가능성과 의도가 보였다.&lt;/p&gt;

&lt;p&gt;아니, 가능성과 의도가 보인 정도가 아니라 출시 버전에 넣기엔 너무 스펙이 크니 줄이자고 제안할 정도였다. 난 글자로 길게 설명하고 떠드는 걸 좋아하는데 박수영 대표는 나보다 더했다. 정말 온갖 정보를 고객에게 주고 싶어했다. 그 마음은 좋지만, 서비스 기획자 경험 상 우리만 힘들지 고객은 제대로 읽지 않을 가능성이 컸다. 박수영 대표 주변에서도 비슷한 피드백을 줬는지 알고리즘을 초기 기획에 비해 좀 더 압축하고(줄인 게 아니라 압축이다) 글자 양도 줄였는데, 여전히 내 기준으로는 너무 많았다. 너무 친절한데… 이것도 팀이 성장하는 과정이라 생각하고 더 잔소리(?)하지 않고 기획대로 구현하기로 했다.&lt;/p&gt;

&lt;p&gt;그런 점에서 Back-end 프로그래밍 언어로 &lt;a href=&quot;https://www.python.org&quot;&gt;Python&lt;/a&gt;이 적합했다. Python은 쉽고 실용적이며 생산성이 좋다. 생태계가 활발하여 좋은 도구도 많으며, 다양한 용도로도 사용할 수 있다. 특히 최근엔 데이터 처리나 분석에 적극 활용되고 있다. 그래서 쇼핑몰과 퀴즈 서비스를 만들기에도 적절하고, 퀴즈 알고리즘이 더 고도화되고 데이터가 많아졌을 때에도 유용하며, 데이터 욕심이 많고 고객이 원하면 재빨리 제공하고 싶어 몸이 달아오르는 박수영 대표의 요청에 대응하려면 쉽고 유연한 Python이 제격이었다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.djangoproject.com&quot;&gt;Django&lt;/a&gt;는 Python으로 웹 애플리케이션 서버를 개발할 때 많이 사용된다. 나는 Django의 여러 기능 중 Admin 기능을 무척 좋아하는데, Django Admin 기능을 이용하여 손쉽고 빠르게, 그리고 확장성 있게 서비스 관리 도구를 개발할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/hello.kaycha/photos/a.1543127105756752.1073741829.439007509502056/1542628302473299/&quot;&gt;&lt;img src=&quot;/assets/uploads/2018/01/django-admin-speech-on-djangogirls-seoul-2017.png&quot; alt=&quot;DjangoGirls Seoul 2017 11월 세미나에서 발표한 “날로 먹는 Django Admin 활용”&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;https://www.facebook.com/hello.kaycha/photos/a.1543127105756752.1073741829.439007509502056/1542628302473299/&quot;&gt;DjangoGirls Seoul 2017 11월 세미나에서 발표한 “날로 먹는 Django Admin 활용”&lt;/a&gt; 중에서&lt;/p&gt;

&lt;p&gt;서비스를 첫 출시할 때엔 관리 도구가 없더라도 당장 문제는 없다. 여차하면 개발자가 출동하여 해결해도 된다. 그러다보니 관리 도구 개발은 후순위로 밀리고 서비스 출시일까지 사용단 기능을 개발하는 데 일정을 맞추기 일쑤이다. 그래도 시간이 촉박하니 이해할 수 있긴 하지만, 달려야 할 때 관리 도구가 미비하여 발목을 잡을 우려가 있다. 서비스 사용량이 늘고 다뤄야 할 자료가 많아지면 협업과 분업이 중요하다. 잘 만든 서비스 관리 도구는 팀의 업무 효율을 높이고 안정성을 높이며, 팀에 필요한 정보를 유통하는 데 기여한다. 그래서 관리 도구를 제대로 준비해놓지 않으면 사업이 성장하느라 한창 바쁠 때 티도 안 나고 개발할 맛도 별로 없는 관리 도구 만드느라 개발팀이 병목이 되기 십상이다.&lt;/p&gt;

&lt;p&gt;물론 서비스 관리 도구를 고도화하는 건 한도 끝도 없다. 저기 앉아 있는 개발자에게서 여유가 느껴지면 막 이것 저것 추가하고 고쳐달라고 말하고 싶다. 하다 못해 차트라도 그려주거나 버튼이라도 예쁘게 해달라고 하고 싶지 않던가. 하지만 한정된 자원으로 높은 실행력을 내야 하는 스타트업은 적정선에서 끊어야 한다. 그 적정선 수준까지는 Django Admin으로도 충분히 빠르고 강력하게 만들 수 있다.&lt;/p&gt;

&lt;p&gt;이외 환경은 사용자가 많거나 자료가 많거나 개발 편의성이 좋은 점을 기준으로 선택했다.&lt;/p&gt;

&lt;h3 id=&quot;front-end&quot;&gt;Front-end&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Web browser : IE 9 이상&lt;/li&gt;
  &lt;li&gt;HTML 5, CSS 3, JavaScript(ECMA 6), Vue.js 2 + Webpack 2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Front-end 기술스택은 기준이 명확했다. 인터넷 익스플로러 8(IE 8) 이하 버전은 지원하지 않는다. 2017년에도 여전히 한국에선 IE 8 점유율을 쓰는 시대 저항 세력이 있지만, 한정된 자원으로 서비스를 개발해야 하는 스타트업의 현실적인 한계가 있었다. 그리고 서비스 이용자 중 모바일 사용자가 훨씬 많을 것이라 확신했다. 난 서비스 출시 버전에선 IE 8를 지원하지 않는 대신 좀 더 나은 기술로 고객의 서비스 사용 경험을 증대시키고 개발 효율을 높이자고 제안했고, 박수영 대표는 흔쾌히 수용했다. 실은 제발 IE 8은 지원하지 맙시다! 에 가까웠다. 😭&lt;/p&gt;

&lt;p&gt;소울부스터는 의류 쇼핑몰이라는 점과 박수영 대표의 성향을 고려 했을 때 사용단 UI 수정이 많을 거라 예상했다. 실제로 매우 많았다. 그래서 현대 UI 프로그래밍에 적합한 도구가 필요했는데, 지금이나 그 당시나 React.js, Angular가 주요 축이다. 난 React.js와 Angular의 장점을 제공하면서도 학습과 활용이 상대적으로 쉬운 &lt;a href=&quot;https://vue.js&quot;&gt;Vue.js&lt;/a&gt;를 선택했다. React.js와 Angular에 비해서는 사용자 층이나 생태계가 크진 않았지만, 빠르게 성장하고 있었고 사용하기 쉬웠으며, 무엇보다도 친절하고 다정한 한국어 자료가 잘 정리되어 있었다. React.js나 Angular 숙련자를 영입하기 쉽지 않다면 구인한 Front-end개발자가 학습해서라도 활용할 상황을 염두에 뒀다. React.js나 Agnular 경험자라면 어렵지 않게 Vue.js를 사용할 수 있으며, 이 셋과 관련된 기술을 모르더라도 Vue.js는  React.js나 Angular에 비해 쉽게 입문할 수 있다고 판단했다. 왜냐하면 내가 그랬으니까.&lt;/p&gt;

&lt;p&gt;시행착오는 있었지만, Vue.js는 만족스러웠다. 부분적으로 그리고 보수적으로 도입했는데, 기술 스택이 너무 복잡해지지 않으면서도 개발 효율이 상승했으며 성능과 기능도 만족스러웠다. 그리고 반짝 두각을 드러내다 사라지지 않고 빠르게 점유율이 늘어서 Vue.js 경험자도 빠르게 늘고 있어서 면접 자리에서 Vue.js를 사용해봤거나 학습 중인 개발자를 만나는 빈도도 늘고 있다.&lt;/p&gt;

&lt;h3 id=&quot;기타&quot;&gt;기타&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Slack/Jandi&lt;/li&gt;
  &lt;li&gt;Trello&lt;/li&gt;
  &lt;li&gt;Bitbucket&lt;/li&gt;
  &lt;li&gt;MailChimp&lt;/li&gt;
  &lt;li&gt;아임포트&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;나는 주로 원격 근무를 한다. 제품이나 서비스를 운영하고 있거나 개발 총괄자가 있는 프로젝트에서 원격 근무는 해봤지만, 프로젝트 시작부터 원격 근무를 해본 적은 없었다. 프로젝트 초기엔 팀과 소통을 많이 하기 때문에 다소 걱정이 들었다. 하지만 소프트웨어 개발자는 나 한 명이니 개발 관련하여 얼굴 맞대고 논의하는 일은 바보같은 코드를 짠 3일 전의 나를 오늘의 내가 구박하는 그런 경우가 대부분이었다. 그래서 일주일에 1~2회 정도 사무실에 출근하고, 소통 채널을 적극 활용하여 원격 근무를 해도 협업에 큰 지장이 없도록 노력했다.&lt;/p&gt;

&lt;p&gt;이슈 빈도가 빈번하지 않거나 외부 상황에 의존하는 일이 일정할 땐 원격 근무가 별 문제는 없었다. 물론 비동기로 소통을 나누기 때문에 동기식 소통에 익숙한 사람에겐 불편할 수도 있다. 다행히 박수영 대표는 팀에 엄청 많은 일을 할당했고, 동기식으로 협업해서는 도저히 일정을 맞출 수 없었다. 각자 알아서 업무 맥락(context)을 관리하며 비동기로 업무를 해치워야 했고, 뜻하지 않게 비동기식 협업이 동작했다. 😂&lt;/p&gt;

&lt;p&gt;나는 온라인 협업 도구 사용으로 비동기로 협업하더라도 효율이 떨어지지 않게 유도하려고 노력했다. 그렇다고 엄격하게 도구 사용 규칙을 정하거나 강제하지 않았다. 핵심 원칙을 하나 정하고, 그 원칙을 지키도록 유도했다. 그 원칙이란 바로 공개와 개방이다. 되도록이면 &lt;a href=&quot;https://trello.com&quot;&gt;Trello&lt;/a&gt;와 &lt;a href=&quot;https://slack.com&quot;&gt;Slack&lt;/a&gt;/&lt;a href=&quot;https://www.jandi.com&quot;&gt;Jandi&lt;/a&gt;를 사용하여 이슈를 팀 내에서는 누구나 확인할 수 있게 공개했다. Slack/Jandi나 email, Trello 어디에도 기록이 남지 않은 업무 요청을 가끔 반려하기도 했다.&lt;/p&gt;

&lt;p&gt;박수영 대표는 서비스 기획자 역할도 하고 있었으므로 사소한 이슈라도 거의 모든 email에 참조자에 참여시켰다. 그래도 정보 불일치 상황을 피하고자 출근할 땐 진행 상황과 주요 이슈를 동기화했다. 몇 가지 아쉬운 점은 있지만, 아직 출시하지 않은 제품이나 서비스를 개발하는 단계에서도 원격 근무가 동작할 수 있는 가능성을 발견하기도 했고, 해결해야 할 문제도 발견했다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://iamport.kr&quot;&gt;아임포트&lt;/a&gt;는 결제 대행(Payment Gateway) 서비스 연동을 대신해주는 중개 서비스이다. 대행을 대신한다니 뭔가 복잡하게 보이는데, 아임포트를 사용하면 번거롭고 짜증나는 PG연동 작업을 아주 간편하게 해치울 수 있다. 게다가 PG를 하나만 연동할 경우 무료이며, 복수 개 PG를 연동하더라도 한 번만 결제하면 되므로 사용하지 않을 이유가 없었다. 게다가 아임포트는 결제 기능 구현 일정에 영향을 미치는 외부 의존도를 상당히 낮춰줬다. 외부 의존도가 높은 이슈가 많을수록 일정 추정이 어려워지고 팀 스트레스를 높이기 때문에 우린 아임포트에게 많은 도움을 받은 셈이다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://mailchimp.com&quot;&gt;MailChimp&lt;/a&gt;는 Non-paid 마케팅 채널 중 하나인 email 마케팅을 하는 데 매우 유용하다. 개발자의 지원이 없이도 email 마케팅을 수행하고 운영하는 데 필요한 기술 요소를 활용할 수 있으며, MailChimp가 머신러닝으로 최적화하여 제안하는 여러 가설과 실행 방법은 비숙련자의 가설과 계획보다 나은 경우가 많다. 그래서 email 마케팅에 필요한 기술 지원에 내가 손을 쓸 일은 없었으며, 회원가입, 주문 안내 등 안내 email을 발송하는 용도로 MailChimp의 서비스 중 하나인 Mandrill사용했다.&lt;/p&gt;

&lt;h2 id=&quot;3-소프트웨어-개발팀-구축&quot;&gt;3. 소프트웨어 개발팀 구축&lt;/h2&gt;
&lt;p&gt;소프트웨어 개발을 해본 적이 없거나 모르는 창업자는 어떤 분야 개발자가 팀에 필요한 지, 경력은 어느 정도여야 하는 지, 어떤 사람이어야 하고, 어떤 기술을 다룰 줄 알아야 하는 지 궁금해하곤 한다. 모두 챙겨야 할 요건이긴 하다. 하지만 전제해야 할 것이 두 가지 있다. 스타트업 팀 구축(building)을 주제로 강연할 때 마다 일관된게 주장하는 내용이기도 하다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;창업자가 해결하고자 하는 문제를 풀고 싶어 하는 사람이어야 한다.&lt;/li&gt;
  &lt;li&gt;회사와 함께 성장하는 지속 가능한 팀을 함께 만들어야 한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;많은 창업자가 개발팀이 필요하다고 하지만 대화를 나눠보면 실제로는 개발팀이 목표가 아니라 제품이나 서비스가 목표인 경우가 많다. 사업의 목적과 목표가 소프트웨어 개발이 아니라면 틀린 생각은 아니지만, 개발팀이 필요한 것과 개발팀이 만들어낼 결과물이 필요한 것은 구분해야 한다. 후자라면 꼭 내부 개발팀을 고집할 필요가 없다. 오히려 내부 개발팀을 구축해서 실행력이 더뎌지거나 품질 낮은 제품을 만들기도 하고, 적지 않은 돈과 시간과 정신 에너지를 쓰고 개발팀을 해체하는 경우가 무척 많다.&lt;/p&gt;

&lt;p&gt;개발팀과 함께 창업의 이유(why)를 함께 고민하고, 그 문제를 풀 방법을(how) 모색하여 고객에게 적합한 제품이나 서비스(what)가 나와야 한다. 그래서 개발자 개개인은 전문 기술 분야를 갖고 있더라도 팀은 문제를 해결하는 데 필요한 기술 스택을 어떻게든 소화할 수(풀스택) 있어야 한다. 풀스택이라 함은 능력이나 경력이 될 수도 있지만 더 중요한 건 문제를 풀겠다는 태도(마음)이다.&lt;/p&gt;

&lt;p&gt;그런 점에서 소울부스터는 개발자 영입이 쉽진 않았다. 아무래도 여성 맞춤 속옷 사업 아이템에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;여성&lt;/code&gt;이 큰 인상을 끼치고, 적어도 내 지인이나 인터뷰를 본 남성 개발자 중에서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;여성&lt;/code&gt; 속옷과 관련된 주제에 문제를 고민하는 경우는 없었다. 그래서 인터뷰를 볼 때 소울부스터가 해결하고자 하는 문제는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;맞춤&lt;/code&gt;이라는 것에 초점을 두고자 했다. 박수영 대표가 내게 들려준 비전과 창업 이유를 최대한 기술과 개발로 풀어서 전달하려 노력했다.&lt;/p&gt;

&lt;p&gt;인터뷰 볼 때 중요시 여긴 또다른 요소는 서비스 운영 경험이었다. 박수영 대표는 실행력이 좋고, 또 성격도 급한 편이다. 필요하거나 중요한 일이면 설령 그게 하기 싫거나 까다로운 일이라도 망설이지 않고 기꺼이 직접 실행하는 사람이다. 이런 대표자의 실행력은 서비스를 운영하여 사업 실적이 바로 바로 발생하는 실 운영 환경에서 개발자에게 압박과 부담으로 작용한다. 그런 상황에 일정 기간 노출된 경험이 필요했다.&lt;/p&gt;

&lt;p&gt;개발팀의 중요 고객 중 하나인 내부 고객, 그러니까 개발팀이 만든 도구를 사용하는 비개발팀 동료을 위한 관리 도구를 고도화하는 업무도 지루하고 재미없는 경우가 많다. 잘 드러나지 않는달까? 그래서 새로운 제품이나 기능을 만드는 건 좋아하지만 서비스 운영에 필요한 개발 업무는 싫어하는 개발자도 있다. 먹고 살아야 하니 일은 하지만 발휘하는 집중력에 차이가 크면 개발자 개인과 개발팀 혹은 회사 모두 불행한 상황에 놓인 것이나 마찬가지이다. 인내할 줄 알아야 한다. 더 나아가 그런 환경에서도 같은 문제에 대해서 더 나은 해결책을 모색하는 호기심을 유지하고 실행하는 체력과 자기 관리를 할 수 있어야 한다. 그걸 넘어서 아예 서비스 운영 개발을 좋아하면 더 좋고.&lt;/p&gt;

&lt;p&gt;박수영 대표에게 이런 생각을 피력했고, 이럴 경우 생각보다 개발팀 구축에 시간이 오래 걸릴 수 있다는 걸 일러두었다. 그 생각보다 오래 걸린 시간이 그 생각보다 더 오래 걸리긴 했지만, 마침내 Front-end와 Back-end 각 한 명씩 구인하는 데 성공했다.&lt;/p&gt;

&lt;p&gt;서비스를 출시한 이후 한결 개발자 구인 기회가 많아졌다. 출시 전에는 아무리 잘 설명해봐야 실제로 동작하는 눈에 보이는 실체가 없으니 회사 정체성은 여성 속옷 쇼핑몰에 그쳤다. 소울부스터에서 고심 끝에 만들고 계속 진보하는 체형 진단 퀴즈 알고리즘이 고객에게 어떤 가치를 주는 지 입사 지원자에게 확신시키기 어려웠다. 하지만 서비스를 출시하고 직접 공장에서 제조한 제품을 고객에게 팔고, 퀴즈 알고리즘을 사용하는 고객 경험과 고객 응대 경험이 쌓이자 팀의 정체성은 더 뚜렷해졌다. 나는 그런 점들이 어우러져 입사 지원 수도 늘고 회사의 비전을 미리 살펴보고 관심있는 사람이 지원하는 빈도가 늘었다고 생각한다.&lt;/p&gt;

&lt;h2 id=&quot;마무리&quot;&gt;마무리&lt;/h2&gt;
&lt;p&gt;함께 프로젝트를 하기로 한 지 만 1년이 됐다. 나는 시간 당 비용을 청구했는데, 서비스 안정화 이후 내가 프로젝트에 들이는 시간이 확 줄었고 그만큼 내 수입도 (무척) 줄었다 😔. 그렇다. 박수영 대표와 함께 정한 우리의 목표에 도달한 것이다. 개발팀을 구축했고, 회사와 팀은 성장하고 매출도 상승하며 연 이륙을 시작하고 있었다.&lt;/p&gt;

&lt;p&gt;얼마 전 우리는 계약을 공식 종료했다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/uploads/2018/01/soulbooster-product-unboxing.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;소울부스터 팀은 여전히 실행력을 유지하고 있고, 제품에 만족하는 고객도 계속 늘고 있는 것 같다. 직접 돈 주고 제품을 산 내 아내도 소울부스터 제품에 만족하고 있다. 그래서 두 번 샀다. 난 프로젝트를 개시하여 출시하고, 운영하며 안정화하고, 개발팀 구축도 함께 하며 작지만 야무지고 단단하게 성장하는 팀을 곁에서 보았기에 여러모로 소울부스터에 정이 간다. 소울부스터의 성공을 기원하며 개발 후기를 마쳐본다.&lt;/p&gt;

&lt;p&gt;아참, 소울부스터에서 시니어 개발자를 충원하고 있다고 한다. 소프트웨어적 상상력을 발휘하기 좋은 사업 아이템이자 사업 비전이라 생각한다. 관심있는 개발자는 recruit@soulbooster.co.kr 로 문을 두드려 보길 추천해 본다. 추천인 코드는 hannal (농담). 아니면 내게 자리를 마련해달라고 연락을 해도 좋다. 위에 명기된 기술 스택을 경험했다면 좋겠지만, 그렇지 않더라도 서로 적합하다면 문제 될 것 없다. 문제가 안 된다는 건 현 소울부스터 개발팀이 살아있는 사례라서 말할 수 있다.&lt;/p&gt;

&lt;p&gt;One more thing. 소울부스터처럼 서비스 개발부터 개발팀 구축, 운영까지, 혹은 프로젝트를 의뢰나 문의하실 분은 kay@hannal.net 으로 문의하세요. 😍&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;2018년 1월 12일 추가 : 소울부스터에서는 샘 방지 위생 팬티인 시즈더데이를 전문으로 취급하는 쇼핑몰인 &lt;a href=&quot;http://www.seizetheday.co.kr/&quot;&gt;시즈더데이&lt;/a&gt;를 2017년 12월에 출시했다. 팬티 제품은 브레지어와 달리 가슴 체형 진단을 하지 않아도 되고, 회원가입을 하지 않아도 손쉽게 주문할 수 있도록 별도 쇼핑몰을 구축한 것인데, 흥미로운 점은 쇼핑몰 호스팅 서비스를 사용한 점이다. 개발팀을 보유한 회사의 대표가 이런 판단을 실제로 실행하는 게 생각보다 쉽지 않다. 정말 사업 측면에서 놀라운 실행력이다. 개발자로서 이런 사업 실행력과 판단에 박수를 보내본다.&lt;/p&gt;
</description>
        <pubDate>Wed, 10 Jan 2018 02:00:00 +0000</pubDate>
        <link>http://blog.hannal.com/2018/01/soulbooster-production-story/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2018/01/soulbooster-production-story/</guid>
        
        <category>소울부스터</category>
        
        <category>soulbooster</category>
        
        <category>개발 후기</category>
        
        <category>팀 빌딩</category>
        
        <category>python</category>
        
        <category>django</category>
        
        <category>vuejs</category>
        
        <category>여성 속옷</category>
        
        <category>쇼핑몰</category>
        
        
        <category>devlife</category>
        
      </item>
    
      <item>
        <title>일하는 방식 고민.</title>
        <description>&lt;p&gt;난 하루에 집중해서 6~8시간, 일주일에 3~4일만 일하는 게 좋을 것 같다.&lt;/p&gt;

&lt;p&gt;딴짓하지 않고 자신을 몽땅 짜낼만큼 높은 집중력을 내면서 항상성을 유지할 수 있는 하루 최대 근무 시간은 6시간이 최대치인 것 같다. 8시간도 큰 부담은 없는데, 사람이 언제나 전력 질주하듯 집중력을 발휘할 수 있는 건 아니니 기준은 하루 6시간 집중하는 데 맞추고 좀 여유를 내고 싶은 날은 8시간 일하면 될 것 같다.&lt;/p&gt;

&lt;p&gt;자신을 짜내듯 일하면 업무 탈진한다. 탈진했다고 퇴사해서 충전하는 건 업무 맥락, 팀웍 구축 비용이 너무 아깝다. 그러므로 평소에(?) 일하듯이 평소에 공부하고 경험하며 충전해야 한다. 나는 느리고 시간을 많이 투입해야 해서 하루에 3시간은 학습에 쓰고, 일주일에 하루 정도는 온전히 경험성 활동에 써야 하는 것 같다. 이것도 멍하니 읽고 감상하는 게 아니라 의식하고 의도한 활동으로 해야 한다. 안 그러면 남는 게 없는 것 같다.&lt;/p&gt;

&lt;p&gt;주 중에 일과 학습에 집중해서 달리려면 그 주를 회고하고 다음 주를 계획하는 데 하루 정도는 써야 한다. 물론 노는 것도 포함해서. 그리고 온전히 쉬는 날은 최소 하루 보장하고.&lt;/p&gt;

&lt;p&gt;이렇게 해서 먹고 살 수 있을까? 일주일에 100시간씩 일해야 성공할 기회가 생긴다고 한다. 성공하거나 시간과 자본에서 어느 정도 자유를 얻은 지인을 보면 그런 것 같다. 물론 죽도록 일해서 결국 죽거나 동력을 잃거나 실패하는 사람은 있다. 그런데 느슨하게(?) 일해서 성공한 경우는 적어도 내 주변엔 없고, 사회에 알려진 사례도 못본 것 같다.&lt;/p&gt;

&lt;p&gt;실은 나 혼자라면 그런 삶에 곧 도달할 것 같다. 문제는 언제까지 지속할 수 있느냐이다. 1~2년이 아니라 5년, 10년, 20년 이후에도 이렇게 일해서 먹고 살 수 있을까? 자본 소득이 뒷받쳐주지 않는 현 상황에서는 낙관하지 못한다.&lt;/p&gt;

&lt;p&gt;나 혼자가 아니라 팀이라면 장기 지속할 가능성이 클 것 같다. 한 사람이 하는 일을 두 사람이 하면 된다. 팀 복잡도가 올라가서 발생하는 누수를 감안하면 두 사람이 소화하는 것보다 한 사람이 하는 게 더 나을지도 모른다. 하지만 예측 가능한 일정과 높은 질을 꾸준하게 내려면 한 사람이 하는 일을 두 사람이 하루 6~8시간, 주 3~4일 근무하며 처리해야 가능할 것 같다. 물론 팀원의 프로 의식과 그런 문화를 지키고 발전시키려는 태도와 마음을 서로 강하게 믿어야 가능하다. 결국 누구와 함께 하느냐가 가장 중요한 문제이다.&lt;/p&gt;

&lt;p&gt;그렇다고 같은 일을 많은 사람이 한 팀으로 모이는 건 아니다. 오히려 일을 쪼개고 쪼갠 일에 대해 온전히 위임하고 권한을 발휘하도록 하여 개개인 간 의존성을 낮춰야 한다. 팀은 다른 조직에 비해 오히려 큰데 일은 다른 조직보다 더 원자화 되어 동작하는 그런 조직을 어떻게 만들어야 할 지 아직은 잘 모르겠다.&lt;/p&gt;
</description>
        <pubDate>Sat, 06 Jan 2018 10:00:00 +0000</pubDate>
        <link>http://blog.hannal.com/2018/01/thinking-of-how-to-work/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2018/01/thinking-of-how-to-work/</guid>
        
        <category>조직</category>
        
        <category>일</category>
        
        <category>팀</category>
        
        
        <category>devlife</category>
        
      </item>
    
      <item>
        <title>Django 템플릿에서 VariableDoesNotExist 예외 오류 대응하기</title>
        <description>&lt;p&gt;한 줄 요약 : Django 템플릿 엔진은 템플릿 필터에 대해서 항상 조용한 실패 처리(silent failure)를 하진 않는다.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Django Template은 없는 템플릿 변수나 템플릿 변수의 속성, 키, 색인이 없어도 오류 상황을 일으키지 않고 조용히 오류 상황을 잠재운다. 일명 Silent failure 동작이다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{{ lorem.ipsum.hello.world }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lorem&lt;/code&gt;이라는 템플릿 변수가 없든 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lorem&lt;/code&gt; 템플릿 변수는 있는데 이 객체에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ipsum&lt;/code&gt;이라는 키나 속성이 없다고 가정하자. 최종 템플릿 맥락이 출력(치환)이면 Django는 변수나 키, 속성이 없다는 오류 상황을 일으키지 않으며, 저 템플릿 변수 위치엔 아무것도 출력되지 않는다. 템플릿 변수를 출력(render)하거나 템플릿 태그에서 사용할 때는 이처럼 Silent failure로 동작한다.&lt;/p&gt;

&lt;p&gt;하지만 템플릿 필터를 거치는 경우엔 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VariableDoesNotExist&lt;/code&gt; 예외(exception)가 발생한다. 예외 이름에서 드러나듯이 템플릿 변수가 없다는 뜻이다.&lt;/p&gt;

&lt;p&gt;예를 들어, 존재하지 않는 템플릿 변수인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;not_exist_var&lt;/code&gt;를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;divisibleby&lt;/code&gt; 템플릿 필터에 사용하면 예외 오류가 발생한다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{{ '1234'|divisibleby:not_exist_var }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이에 대해 Django 공식 문서에서는 다음과 같이 설명한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Thus, filter functions should avoid raising exceptions if there is a reasonable fallback value to return. In case of input that represents a clear bug in a template, raising an exception may still be better than silent failure which hides the bug. ( 출처 : &lt;a href=&quot;https://docs.djangoproject.com/en/1.11/howto/custom-template-tags/#writing-custom-template-filters&quot;&gt;custom-template-tags - writing-custom-template-filter&lt;/a&gt; )&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;간단히 말해서 템플릿 필터 함수에서는 버그를 숨기는 Silent failure 보다는 예외를 일으키는 게 낫다고 한다. 실제로 Django 내장 템플릿 필터를 보면 대체물을 대신 반환해도 될 만한 경우엔 Exception 처리를 잡아내서 오류 상황을 피하지만, 그 외의 경우엔 Exception이 발생하게 냅둔다. 문제는 그 정책이 예상을 벗어나는 경우에 발생한다. 난 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt; 템플릿 필터에서 조용한 실패 처리를 하지 않는 상황을 만났다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt; 템플릿 필터는 대개 다음과 같이 사용한다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{{ empty_var|default:'비었수다' }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;나도 비슷하게 사용했다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{{ apple.attr3|default:lemon.attrdict.color }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Django 템플릿 엔진 동작에 익숙하다면 다음과 같이 동작하길 기대(예상)한다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apple.attr3&lt;/code&gt;가 없으면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lemon.attrdict['color']&lt;/code&gt;를 대신 출력&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lemon.attrdict&lt;/code&gt;에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color&lt;/code&gt; 키가 없으면 결국 아무것도 출력하지 않고 Silent failure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;딱히 Exception이 발생할만한 로직이 아니고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt; 필터 함수를 봐도 인자 두 개 받아서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return value or arg&lt;/code&gt;로 동작하는 것 뿐이다. 다시 말해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt; 템플릿 필터인 Python 함수는 두 개 인자를 받는데, 첫 번째 인자로 받는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;|&lt;/code&gt; 앞에 있는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apple.attr3&lt;/code&gt;가 있으면 해당 객체를 반환하고, 없으면 두 번째 인자로 받는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:&lt;/code&gt; 뒤에 있는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lemon.attrdict.color&lt;/code&gt;를 반환한다.&lt;/p&gt;

&lt;p&gt;하지만 실제로는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VariableDoesNotExist&lt;/code&gt; 예외 오류가 발생한다. 이 예외는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with&lt;/code&gt; 템플릿 태그로 해결하면 된다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://gist.github.com/hannal/9de33b54a749457d7f29c5f30c5e9136&quot;&gt;with 템플릿 태그로 VariableDoesNotExist 예방&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with&lt;/code&gt; 템플릿 태그는 Silent failure 처리를 해주니 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with&lt;/code&gt; 템플릿 태그로 만든 임시 템플릿 변수인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;colour&lt;/code&gt;엔 출력할(render) 게 없는 빈 객체가 할당이 된다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with&lt;/code&gt; 템플릿 태그를 안 쓴 경우엔 Silent failure를 해주는 놈이 없다보니 Exception이 그대로 나버린 것이다. 이 문제가 까다로운 이유는 Django 디버깅 화면에서는 문제가 있는 템플릿 줄(line)을 가리키지 않고 Exception이 발생한 Django 소스를 보여주는 데 있다. 평범한 속성명이나 키 이름을 쓰다가는 고생하기 십상이다.&lt;/p&gt;

&lt;p&gt;흔히 겪는 상황은 아닐 것 같다. 나는 모델에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSONField&lt;/code&gt;를 썼고, 이 모델필드의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dict&lt;/code&gt; 객체에 특정 키(위 예제 기준으로는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;colour&lt;/code&gt;키)가 없어서 발생한 거였다. 뷰 함수에서 넘겨주는 템플릿 변수 이름대로라면 금방 발견했을 것 같다.&lt;/p&gt;

&lt;p&gt;삼천포 요약 : 변수 네이밍을 괴랄하게 하면 디버깅에 도움이 된다.&lt;/p&gt;
</description>
        <pubDate>Mon, 26 Jun 2017 00:00:00 +0000</pubDate>
        <link>http://blog.hannal.com/2017/06/django-template-variabledoesnotexist-exception/</link>
        <guid isPermaLink="true">http://blog.hannal.com/2017/06/django-template-variabledoesnotexist-exception/</guid>
        
        <category>django</category>
        
        <category>template</category>
        
        <category>템플릿</category>
        
        
        <category>devlife</category>
        
      </item>
    
  </channel>
</rss>
