지금까지 Dockfile를 만드록 Docker 컨테이너를 테스타했던 경험을 바탕으로 팁을 작성해본다.



1. 항상 Docker image는 최대한 작게 만들어야 한다.


처음 운영체제 Docker 이미지가 작더라도 조금씩 바이너리 또는 응용 프로그램을 설치하면 용량이 꺼진다. 


특히, yum update 또는 필요 없는 툴은 최대한 설치하지 않는다. 생각보다 툴은 용량을 많이 차지한다. 


용량이 꺼지면, 다음의 단점이 있다.

1. 배포를 해야 할 때, 다운로드해야 하기 때문에, 점점 속도가 느려진다.

2.용량이 크면 docker registry push 또는 docker hub push 할 때, 종종 에러가 발생할 수 있다. 계속 retry 해야 한다.. 

(아직 docker registry가 완벽하지 않다.)





2. Dockerfile 를 생성할 때는 설치 단위를 RUN 옵션으로 설정한다. 


Docker image는 history별로 layering이 되어 있다. 더 정확히 말하면, Dockerfile의 RUN 커맨드 하나가 history layer이다.  따라서 커맨드를 &&로 붙여 적당한 추상화를 할 수 있다.


apt-get update && apt-get install -y apache2 && apt-get clean && rm -rf /var/lib/apt/lists/*



docker history로 이미지를 보면, 마지막 컬럼이 SIZE이다. 내가 하는 모든 RUN에 대한 용량 체크를 할 수 있다. 



임시 파일/디렉토리 또는 /tmp 또는 wget으로 받은 압축 파일이 존재하는지 확인하고, 용량을 최대한 줄일 수 있도록 해야 한다. 





3. Docker image를 만들다 실패하더라도, 도커 이미지를 지우지 말고 다시 생성을 시도한다.


copy on write 방식이라, 기존에 쓰던 Docker image의 단계별 해시 정보가 있다면 바로 복사해서 쓰기 때문에, 최종 도커 이미지를 생성하기 전까지는 기존의 실패했던 이미지를 최대한 활용하는 방식이 좋다.



하지만, 앞 부분의 RUN 커맨드에서 변경이 일어나면, 뒷 부분까지 모두 해시 정보가 바뀔 수 있기 때문에 Dockerfile 전략을 잘 사용하는 것이 중요하다.


즉, Dockerfile에 새로운 설치를 추가할 때, 맨 마지막에 두어야 한다. 그 부분만 수정한 후, 다시 도커 이미지를 생성할 수 있기 때문에 빠르게 빌드할 수 있다. 





4. 도커 이미지 파일의 용량이 크다는 것은 그만큼 디스크와 CPU를 많이 소비할 수 있는 확률이 생길 수 있다.


Dockerfile을 최적화해도 용량이 늘어난다는 얘기는 계속 사용할 솔루션이 많다는 얘기이다.


8 cpu, 16G 장비에 4 GB가 안되는 도커 이미지를 컨테이너로 사용할 때, 거의 꽉차게 사용될 수 있다. 도커 이미지 특성상 부하가 많을 수 있기 때문에, 늘 테스트를 할 필요가 있다. 도커 컨테이너로 쓰일 장비를 늘 모니터링하고 좋은 장비를 사용하여 성능을 높일 수 있도록 노력해야 한다. 


Posted by '김용환'
,

외부 서버와 통신할 때, 결과를 enum으로 처리하는 경우가 종종 있다. 

String 리턴 값을 enum으로 변환하기 위해 자바 enum 타입으로 매핑하기 위해 작업하다가 NPE가 발생할 수 있다. 



java.lang.NullPointerException: Name is null

        at java.lang.Enum.valueOf(Enum.java:236)

        at ResultType.valueOf(ResultType.java:6)  // enum이 클래스로 변환하기 때문에 정확히 코드로 보이지 않는다. 






자바에서 enum을 정의하면, 자바 컴파일러에 의해 클래스로 변환된다. 

 (확인 방법은 http://varaneckas.com/jad/를 다운받고, http://knight76.tistory.com/entry/Jad-%EC%82%AC%EC%9A%A9%EB%B2%95로 사용한다)


public enum ResultType {

  SUCCESS("success"),

  ERROR("error");



  //..


// 내부 enum 필드를 map으로 저장한다.

private static Map<String, String> enumMap;


public static String getResult(String name) {

if (enumMap == null) {

    initializeMap();

}

    return enumMap.get(name);

}


private static Map<String, String> initializeMap() {

  enumMap = new HashMap<String, String>();

  for (ResultType resultType : resultType.values()) {

    enumMap.put(resultType.getName(), resultType.toString());

  }

  return enumMap;

 }

}



=>



  6 public final class ResultType extends Enum

  7 {

...


// 새로 추가되는 메소드

 50     public static ResultType valueOf(String s)

 51     {

 52         return (ResultType)Enum.valueOf(ResultType, s);

 53     }

 ..

// 나머지는 그대로

 



외부 서버와 통신한 후, 리턴 받은 값을 enum 으로 변환하기 위해 

String.valueOf()와 ResultType.getResult()를 사용하, 다시 이를 ResultType.valueOf()로 적용하면 원하는 값으로 바인딩 될 수 있다.


String result = restTemplate.exchange(...);

ResultType.valueOf(ResultType.getResult(String.valueOf(result)))



서로 약속한 값이 전달되면 문제는 없지만, 외부 통신 서버에서 결과 값을 이상하게 전달할 때, 에러가 발생한다. 


String result = restTemplate.exchange(...); // enum에서 정의되지 않은 "xxxxx"라는 스트링이 도착했다.

ResultType.valueOf(ResultType.getResult(String.valueOf(result)))


==> NPE : Name is null




외부 서버가 enum에 없는 값을 보내면서 ResultType.getResult()는 null을 리턴하고, enum이 클래스로 변환하면서 만들어진 ResultType.valueOf() 메소드는 내부적으로 Enum.valueOf()를 호출한다. 이 때 두 번째 매개변수가 null이 된다. Enum.valueOf() 메소드는 내부적으로 아래와 같이 되어 있고, name이 null이면 NPE 가 발생한다.


 


http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/Enum.java#Enum.valueOf%28java.lang.Class%2Cjava.lang.String%29


207    public static <T extends Enum<T>> T More ...valueOf(Class<T> enumType,

208                                                String name) {

209        T result = enumType.enumConstantDirectory().get(name);

210        if (result != null)

211            return result;

212        if (name == null)

213            throw new NullPointerException("Name is null");

214        throw new IllegalArgumentException(

215            "No enum const " + enumType +"." + name);

216    }




따라서 외부 서버에서 들어온 값을 enum으로 변환할 때는 enum에서 선언한 필드가 아닌 값이 올 수 있고, 내부 코드에 따라 enum을 null로 바인딩될 수 있다는 것을 가정하고 코드를 짜는 것이 좋다.



String result = restTemplate.exchange(...);


if (ResultType.getResult(String.valueOf(result)) == null) {

 // 통신 에러 처리 

} else {

 ResultType type = ResultType.valueOf(ResultType.getResult(String.valueOf(result)))

 // 정상 처리

}






Posted by '김용환'
,


리스트(List)에서 중복 요소 제거할 때, 다음 예시를 사용한다.


#!/bin/bash


contents=("a1123zba" "bbbbbbb" "ccccc" "ccccc")

result=`echo "${contents[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '`

echo $result




결과는 다음과 같다.


$ ./test.sh

a1123zba bbbbbbb ccccc


Posted by '김용환'
,


테스트 용도로 hbase 1.x.x(hbase 1.0, hbase 1.2, hbase1.2.2)를 설치하고, hbase.rootdir를 로컬 디렉토리로 정하고(로컬 디렉토리로 정하지 않아도 에러가 발생했다), hbase-site.xml에 정의한 후, standalone으로 실행할 때, 다음과 같은 에러가 발생할 수 있다. 



2016-08-02 20:49:27,597 FATAL [Samuelui-MacBook-Pro:59298.activeMasterManager] master.HMaster: Failed to become active master

java.net.ConnectException: Call From Samuelui-MacBook-Pro.local/1.1.1.1 to localhost:9000 failed on connection exception: java.net.ConnectException: Connection refused; For more details see:  http://wiki.apache.org/hadoop/ConnectionRefused

at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)

at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)

at java.lang.reflect.Constructor.newInstance(Constructor.java:422)

at org.apache.hadoop.net.NetUtils.wrapWithMessage(NetUtils.java:783)

at org.apache.hadoop.net.NetUtils.wrapException(NetUtils.java:730)

at org.apache.hadoop.ipc.Client.call(Client.java:1415)

at org.apache.hadoop.ipc.Client.call(Client.java:1364)

at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:206)

at com.sun.proxy.$Proxy16.setSafeMode(Unknown Source)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at org.apache.hadoop.io.retry.RetryInvocationHandler.invokeMethod(RetryInvocationHandler.java:187)

at org.apache.hadoop.io.retry.RetryInvocationHandler.invoke(RetryInvocationHandler.java:102)

at com.sun.proxy.$Proxy16.setSafeMode(Unknown Source)

at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.setSafeMode(ClientNamenodeProtocolTranslatorPB.java:602)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at org.apache.hadoop.hbase.fs.HFileSystem$1.invoke(HFileSystem.java:279)

at com.sun.proxy.$Proxy17.setSafeMode(Unknown Source)

at org.apache.hadoop.hdfs.DFSClient.setSafeMode(DFSClient.java:2264)

at org.apache.hadoop.hdfs.DistributedFileSystem.setSafeMode(DistributedFileSystem.java:986)

at org.apache.hadoop.hdfs.DistributedFileSystem.setSafeMode(DistributedFileSystem.java:970)

at org.apache.hadoop.hbase.util.FSUtils.isInSafeMode(FSUtils.java:525)

at org.apache.hadoop.hbase.util.FSUtils.waitOnSafeMode(FSUtils.java:971)

at org.apache.hadoop.hbase.master.MasterFileSystem.checkRootDir(MasterFileSystem.java:424)

at org.apache.hadoop.hbase.master.MasterFileSystem.createInitialFileSystemLayout(MasterFileSystem.java:153)

at org.apache.hadoop.hbase.master.MasterFileSystem.<init>(MasterFileSystem.java:128)

at org.apache.hadoop.hbase.master.HMaster.finishActiveMasterInitialization(HMaster.java:638)

at org.apache.hadoop.hbase.master.HMaster.access$500(HMaster.java:184)

at org.apache.hadoop.hbase.master.HMaster$1.run(HMaster.java:1729)

at java.lang.Thread.run(Thread.java:745)

Caused by: java.net.ConnectException: Connection refused

at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)

at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)

at org.apache.hadoop.net.SocketIOWithTimeout.connect(SocketIOWithTimeout.java:206)

at org.apache.hadoop.net.NetUtils.connect(NetUtils.java:529)

at org.apache.hadoop.net.NetUtils.connect(NetUtils.java:493)

at org.apache.hadoop.ipc.Client$Connection.setupConnection(Client.java:606)

at org.apache.hadoop.ipc.Client$Connection.setupIOstreams(Client.java:700)

at org.apache.hadoop.ipc.Client$Connection.access$2800(Client.java:367)

at org.apache.hadoop.ipc.Client.getConnection(Client.java:1463)

at org.apache.hadoop.ipc.Client.call(Client.java:1382)

... 29 more



2016-08-03 10:53:19,227 DEBUG [main-SendThread(fe80:0:0:0:0:0:0:1%1:2181)] zookeeper.ClientCnxnSocketNIO: Ignoring exception during shutdown input

java.net.SocketException: Socket is not connected

        at sun.nio.ch.Net.translateToSocketException(Net.java:145)

        at sun.nio.ch.Net.translateException(Net.java:179)

        at sun.nio.ch.Net.translateException(Net.java:185)

        at sun.nio.ch.SocketAdaptor.shutdownInput(SocketAdaptor.java:404)

        at org.apache.zookeeper.ClientCnxnSocketNIO.cleanup(ClientCnxnSocketNIO.java:200)

        at org.apache.zookeeper.ClientCnxn$SendThread.cleanup(ClientCnxn.java:1185)

        at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1110)

Caused by: java.nio.channels.NotYetConnectedException

        at sun.nio.ch.SocketChannelImpl.shutdownInput(SocketChannelImpl.java:782)

        at sun.nio.ch.SocketAdaptor.shutdownInput(SocketAdaptor.java:402)

        ... 3 more



2016-08-02 16:07:11,339 WARN  [RS:0;172.26.116.174:50808-SendThread(fe80:0:0:0:0:0:0:1%1:2181)] zookeeper.ClientCnxn: Session 0x1564a13ad290003 for server null, unexpected error, closing socket connection and attempting reconnect

java.net.ConnectException: Connection refused

at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)

at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)

at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:361)

at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1081)






에러가 발생하는 코드는 다음과 같다.

https://github.com/apache/hbase/blob/branch-1.0/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java



        MonitoredTask status = TaskMonitor.get().createStatus("Master startup");

        status.setDescription("Master startup");

        try {

          if (activeMasterManager.blockUntilBecomingActiveMaster(timeout, status)) {

            finishActiveMasterInitialization(status);

          }

        } catch (Throwable t) {

          status.setStatus("Failed to become active: " + t.getMessage());

          LOG.fatal("Failed to become active master", t);

          // HBASE-5680: Likely hadoop23 vs hadoop 20.x/1.x incompatibility

          if (t instanceof NoClassDefFoundError &&

              t.getMessage().contains("org/apache/hadoop/hdfs/protocol/FSConstants$SafeModeAction")) {

            // improved error message for this special case

            abort("HBase is having a problem with its Hadoop jars.  You may need to "

              + "recompile HBase against Hadoop version "

              +  org.apache.hadoop.util.VersionInfo.getVersion()

              + " or change your hadoop jars to start properly", t);

          } else {

            abort("Unhandled exception. Starting shutdown.", t);

          }

        } finally {

          status.cleanup();

        }




문제를 해결하기 위해 네트워크를 확인했다.


1) ssh 확인

22 번 포트로 ssh 키로 잘 접속되는지 확인.

ssh localhost 


2) 방화벽 확인

맥의 Perference의 Sharing icon를 선택해서 방화벽을 오픈되는지 확인

telnet localhost 123123


3) hdfs 또는 file 확인


hbase-site.xml 파일에서 다음으로 작성. 로그 상 9000번 포트로 접근하는 걸 봐서는 hdfs로 접근하는 것 같아서, file:///을 사용해서 변경하니 잘 작동했다.


 <property>

    <name>hbase.rootdir</name>

    <value>/tmp/hbase/data</value>

  </property>


        


==> 


 <property>

    <name>hbase.rootdir</name>

    <value>file:///tmp/hbase/data</value>

  </property>




Posted by '김용환'
,