블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

효율적인 도커 이미지 만들기

#2 도커 레이어 캐슁을 통한 빌드/배포 속도 높이기

조대협 (http://bcho.tistory.com)


도커는 이미지 단위로 빌드를 하고 배포를 하지만, 도커의 이미지는 여러개의 레이어로 되어 있다. 아래와 같은 자바 애플리케이셔을 패키징한 도커 컨테이너 이미지가 있다고 하자

FROM openjdk:8-jre-alpine

ARG ./target/hellospring-0.0.1-SNAPSHOT.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-jar","/app.jar"]


이 이미지가 어떤 레이어로 구성되어 있는지를 보려면 %docker history {컨테이너 이미지명} 을 실행하면 이미지의 레이어를 볼 수 있는데,   각각의 명령항에 따라서 레이어가 생성된것을 볼 수 있다. 



그리고 각각의 크기를 확인할 수 있다. 위의 history를 보면, 




  • 맨 아래 5.53M가 알파인 리눅스의 베이스 이미지

  • 그리고 위에서 4번째 79.4M가 JRE 설치

  • 위에서 2번째가 33.8M로, 애플리케이션 파일 Jar을 복사한 내용

으로 확인할 수 있다. 계층은 여러개이지만, 주로 용량을 많이 사용하는 이미지는 이 3개의 계층임을 확인할 수 있다.


이 컨테이너에서 애플리케이션을 다시 컴파일 하고 패키징해서 push를 해보면 아래와 같이 33.82M에 해당하는 애플리케이션 Jar 만을 복사하고 나머지는 “Layer already exist”와 함께 별도로 레이어를 푸쉬하지 않는 것을 확인할 수 있다. 


도커 컨테이너는 효과적인 배포를 위해서 pull/push 시에, 레이어별로 캐슁을 해서, 변경이 없는 레이어에 대해서는 다시 pull/push를 하지 않는다

자바 애플리케이션을 여러개의 레이어로 나눈 경우

이 레이어별 캐슁 기능을 잘 활용하면, 애플리케이션 컨테이너의 push/pull 시간을 많이 단축시킬 수 있다. 첫번째 push/pull은 전체 이미지를 올려야하기 때문에 시간 차이는 없겠지만, 그 후 애플리케이션만을 업데이트할 경우에는 애플리케이션에 관련된 파일만 실제로 복사되도록하고, 나머지 레이어는 캐슁된 이미지를 사용하도록 하는 방법이다.


Springboot와 같은 자바 애플리케이션은 애플리케이션 파일은 /classes에 저장되지만, 나머지 참조하는 여러 jar 파일들이 있다. 이러한 jar 파일들은 변경이 없기 때문에, 다시 재배포할 필요가 없기 때문에 캐슁을 하도록 하는 것이 좋다.


그러나 앞의 예제에서는 이 모든 라이브러리 jar 파일들을 하나의 app.jar 에 묶어서 복사하는 구조이기 때문에, 라이브러리 jar 파일만을 별도로 캐슁할 수 없다. 

그래서, 아래와 같이 애플리케이션 jar 파일을 푼 다음에, 각각의 디렉토리를 별도로 복사하는 방법을 사용한다. mvn package 에 의해서 생성된 jar 파일을 target/depdency 라는 디렉토리에 풀고, 각 디렉토리를 Dockerfile에서 각각 복사하도록 설정하였다. 


FROM openjdk:8-jre-alpine

ARG DEPENDENCY=target/dependency

COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib

COPY ${DEPENDENCY}/META-INF /app/META-INF

COPY ${DEPENDENCY}/BOOT-INF/classes /app

COPY ${DEPENDENCY}/org /org


ENTRYPOINT ["java","-cp","app:app/classes/*","com.terry.HellospringApplication"]


이렇게 하게 되면, 사용자가 작성한 코드는 /classes 에 컴파일 된 상태로 저장되고, 나머지 라이브러리들은 /BOOT-INF/lib  등의 디렉토리에 저장된다. 이를 각각 Dockerfile에서 COPY 명령으로 복사하게 되면 별도의 레이어로 생성된다. 

아래는 위의 Dockerfile을 이용해서 빌드한 이미지의 레이어이다. 



앞의 예제의 경우 애플리케이션 jar 파일이 33.82M의 단일 레이어로 생성되었지만, 이 컨테이너는 176k,1.34k,1.84k,16.7M  4개의 레이어로 생성된것을 확인할 수 있다. 




이중에서 1.84k,16.7M 레이어는 Springboot 라이브러리이기 (jar파일) 때문에 변경이 없다. 그래서 캐슁이 가능한데, 


아래는 이 컨테이너를 한번 등록해놓고, 그 다음 애플리케이션 코드를 변경해서 새로 빌드하고 푸슁하는 과정이다. 


보는 것과 같이 위의 2 레이어만 새롭게 푸슁되는 것을 확인할 수 있다. 

이렇게 하면, 아래 OS 레이어, JRE, 그리고 Spring boot의 jar 라이브러리들은 모두 캐슁되고, 배포시에는 실제로 애플리케이션 class 파일만 전송되게 되기 때문에 배포 시간을 많이 단축할 수 있다.


이때 주의 할점은 기존 디렉토리에

BOOT-INF/classes

BOOT-INF/lib

META/lib

org


이런 디렉토리들이 있는데, mvn으로 새롭게 jar를 빌드한후 target/dependecy 파일에 전체 파일을 풀어버리게 되면 4개의 디렉토리가 모두 업데이트가 된다. BOOT-INF/lib,META/lib,org 디렉토리의 실제 파일 내용이 변경이 되지 않았다 하더라도, 새로운 파일로 업데이트하였기 때문에, 파일의 해쉬값이 변경되게 되고, Docker 는 빌드시에, 이 디렉토리가 변경이 되었다고 판단하고 기존의 캐슁된 레이어를 재활용하지 않고 새롭게 레이어를 생성해서 push 하기 때문에, 실제 캐쉬 효과를 볼 수 없다. 

그래서 빌드 과정에서 기존에 변경되지 않은 파일과 디렉토리는 건들지 않고, 변경된 파일만 업데이트 하도록 빌드를 구성해야 한다. 


여기서는 자바 애플리케이션을 기준으로 설명하였지만, Javascript나, 이미지와 같은 정적 파일이 있거나 라이브러리나 드라이브 설치등의 중복되는 부분이 많은 애플리케이션의 경우에는 같은 원리로 각각 레이어를 나눠 놓으면, 캐슁을 통한 push/pull 속도 절약의 효과를 볼 수 있다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

간단한 웹뷰 컴포넌트의 이해와 최적화

조대협(http://bcho.tistory.com)


들어가기에 앞서서


이글은 안드로이드 프로그래밍 개발을 공부하면서 공부한 내용을 정리한 글입니다. 제가 아직 안드로이드에는 전문성이 없는 초보자 수준이기 때문에, 수준이 높지 않으니, 내용을 참고만 하시기를 바라며, 더 나은 프로그래밍 방법등이 있음을 미리 알려드립니다.


Webview(웹뷰) 컴포넌트에 대해서


웹뷰는 안드로이드 프레임웍에 내장된 웹 브라우져 컴포넌트로 뷰(View)형태로 앱에 임베딩이 가능하다. 비교해서 생각하자면, 안드로이드 앱안에, HTML iframe을 넣어놓은 것이라고나 할까?

단순하게, 웹 페이지를 보기위해도 사용하지만, 안드로이드 앱 안에서 HTML을 호출하여 앱을 구현하는 하이브리드 형태의 앱을 개발하는데에도 많이 사용된다. 이러한 하이브리드 앱은 안드로이드 네이티브 앱 개발에 비해서 개발이 비교적 쉽고, 특히나 기기간의 호환성을 해결하기가 상대적으로 편하다는 장점을 가지고 있으나, HTML 기반인 만큼 상대적으로 반응성이 약하고, 애니메이션등의 다양한 UI 효과를 넣기 어렵다는 단점을 가지고 있다.

그럼에도 불구하고 SNS나 기타 서비스 앱에서는 타 웹사이트 링크로 가는 기능등을 지원하기 위해서 많이 사용되고 있다.

웹 뷰에 사용되는 브라우져 컴포넌트는 우리가 일반적으로 생각하는 웹브라우져 수준으로 생각하면 곤란하다. 일반적인 웹 브라우져를 안드로이드 OS에 맞게 일부 기능들을 제외하고 작게 만든 웹브라우져라고 생각하면 되고 그렇기 때문에 HTML5 호환성등 기능의 제약을 많이 가지고 있다.



기본적인 웹뷰 사용방법


이해를 돕기 위해서 웹뷰를 이용한 간단한 애플리케이션을 만들어보자. 이 애플리케이션은 URL을 입력 받고 Go 버튼을 누르면 웹뷰에 해당 URL을 접속하여 보여주고, 해당 URL이 로딩되는데 까지 걸린 시간을 millisecond 로 표시해주는 애플리케이션이다.




먼저 안드로이드 스튜디오에서, MainActivity하나만 있는 심플한 앱 프로젝트를 생성한 후에, MainActivity의 레이아웃을 다음과 같이 디자인하자




위의 레이아웃의 XML 코드를 보면 다음과 같다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity"
android:weightSum="1">


<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<EditText
android:layout_width="252dp"
android:layout_height="wrap_content"
android:id="@+id/txtURL"
android:text="http://www.naver.com" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Go"
android:id="@+id/btnGo"
android:onClick="goURL" />
</LinearLayout>

<WebView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/webView"
android:layout_weight="0.83" />

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.13">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Elapsed time :"
android:id="@+id/tvElapsedTime" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="sec"
android:id="@+id/tvSec" />
</LinearLayout>

</LinearLayout>

레이아웃이 디자인 되었으면, [GO]버튼을 눌렀을때, 웹뷰에 URL을 표시하도록 goURL(View)라는 메소드를 다음과 같이 구현하자


public void goURL(View view){
TextView tvURL = (TextView)findViewById(R.id.txtURL);
String url = tvURL.getText().toString();
Log.i("URL","Opening URL :"+url);

WebView webView = (WebView)findViewById(R.id.webView);
webView.setWebViewClient(new WebViewClient()); // 이걸 안해주면 새창이 뜸
webView.loadUrl(url);

}


사용법은 의외로 간단한데, Webview객체를 얻어와서 loadURL로 url을 로딩하면 된다. 이때 주의할점은 setWebViewClient()  메서드를 사용해야 하는데, 안드로이드에서는 디폴트로, 다른 링크로 이동하고자 할때는 안드로이드의 디폴트 외부 웹 브라우져를 통해서 이동하도록 하고 있다. 그래서 만약 setWebViewClient로 내부 웹뷰 클라이언트를 지정해주지 않으면 별도의 브라우져가 뜨는 것을 볼 수 있다.

그리고 이 애플리케이션은 외부 인터넷을 억세스하기 때문에, AndroidManifest.xml에서INTERNET 퍼미션을 추가해줘야 한다.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.terry.simplewebview" >
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true"> // 하드웨어 가속 사용
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>


주목해서 봐야할점중 하나는 android:hardwareAccelerated  라는 필드인데, 웹 페이지를 렌더링 하는데 하드웨어 가속 기능을 사용하도록 하는 옵션으로, 이 옵션 사용 여부에 따라서 성능차이가 꽤 있기 때문에, 가급적이면 사용하도록 한다.


여기서 약간 개량을 해서, 해당 URL이 다 뜨고 나면 로딩하는데 걸리는 시간을 측정하는 로직을 추가해보자


public void goURL(View view){
TextView tvURL = (TextView)findViewById(R.id.txtURL);
String url = tvURL.getText().toString();
Log.i("URL", "Opening URL with WebView :" + url);

final long startTime = System.currentTimeMillis();
WebView webView = (WebView)findViewById(R.id.webView);

// 하드웨어 가속
// 캐쉬 끄기
//webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);

webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
long elapsedTime = System.currentTimeMillis()-startTime;
TextView tvSec = (TextView) findViewById(R.id.tvSec);
tvSec.setText(String.valueOf(elapsedTime));
}
});
webView.loadUrl(url);

}


setWebViewClient를 만들때, WebViewClient에 onPageFinished라는 메서드를 오버라이딩해서 종료 시간을 구할 수 있다. 이 메서드는 해당 URL이 모두 로딩되었을때 호출되는 메서드이다.


웹뷰의 개념 이해를 돕기 위해서 가장 간단한 예제를 통해서 설명하였는데, HTML 안에서 Javascript 를 사용하려면 별도의 옵션이 필요하고, 또한 javascript를 사용할때는 Javascript의 메서드를 실행하면, 안드로이드 애플리케이션내에 정의된 메서드를 수행할 수 있도록도 할 수 있다. 

기타 자세한 내용은 http://developer.android.com/guide/webapps/webview.html 를 참고하기 바란다.


웹뷰의 성능 향상과 확장


웹뷰에 대한 성능 관련 자료를 찾아보니, 성능이 안나오는 문제가 많아서 하이브리드 앱을 개발하는 쪽에는 다양한 솔루션이 제시되고 있다. 써드 파티 웹뷰 라이브러리를 이용하는 방법이나 또는 웹뷰를 커스터마이징 해서 WebSocket의 기능을 추가하거나 Canvas 성능을 개선하는 작업등이 있는데 (http://deview.kr/2013/detail.nhn?topicSeq=14) 성능 부분에 대해서는 조금 더 공부가 필요할듯 하고, 아래는 성능 관련해서 검색을 했을때 주로 많이 소개되는 내용을 정리하였다.


크롬뷰 사용


안드로이드 4.4 (API level 19)부터는 웹뷰에 대한 성능 개선의 일환으로 Chromium (http://www.chromium.org/Home

) 이라는 오픈소스( 브라우져가 사용되었는데, 별도의 설정없이도, 이 브라우져 기반의 웹뷰가 사용이 가능하다. Chromium 기반의 웹뷰는 성능 향상뿐만 아니라, HTML 호환성 개선도 같이 이루어졌다. 

https://www.google.co.kr/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=webview+performance+android&newwindow=1&start=10


일반적인 튜닝 옵션


일반적으로 웹뷰 성능 튜닝 관련 내용을 검색하면 단골로 나오는 3가지 튜닝 옵션들이 있는데 다음과 같다

  • 캐쉬 사용하지 않기 : webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);로 브라우져 캐쉬를 끄는 방법이 언급되는데, 사실 캐쉬를 쓰는데 왜 성능이 올라가는지는 잘 모르겠다. 이 부분은 조금 더 살펴봐야할것으로 보이고
  • 하드웨어 가속 기능 사용하기 :렌더링에 대해서 하드웨어 가속 기능을 사용할지 여부인데, 3.0 부터 지원이 되기 시작했고, 디폴트는 false이며, 4.0부터는 default가 true이다. 앞의 예제에서와 같이 AndroidManifest.xml에서, android:hardwareAccelrated를  true로 설정해주면 된다.
  • 렌더링 우선 순위 높이기 : 강제적으로 렌더링 기능에 대한 우선 순위를 높이는 방법이다. webView.getSettings().setRenderPriority(RenderPriority.HIGH)로 설정해주면 되는데, API Level 18부터는 deprecated 되었기 때문에 하위 버전에만 적용될 수 있도록 한다.

외부 라이브러리를 이용한 웹뷰의 성능 향상


다른 성능 향상 방법으로는 써드 파티 라이브러리를 찾아봤는데, 가장 많이 사용되는 라이브러리로 크로스워크(crosswalk)라는 오픈 소스 라이브러리가 있다. https://crosswalk-project.org/documentation/getting_started.html

성능 향상은 물론이거니와, HTML5에 대한 호환성 지원도 매우 높고, 안드로이드뿐만 아니라, iOS,Tizen등 다양한 OS를 지원하기 때문에 하이브리드 앱을 개발한다면 탑재를 고려해볼만하지만, 이러한 호환성이나 성능을 높이기 위해서 렌더링 관련 기능을 모두 자체 구현하였기 때문에 앱 사이즈가 대략 30~40MB정도 커지기 때문에, 앱 크기에 대한 부담과 dex 사이즈에 대한 부담 (안드로이드 실행 파일인 dex는 메서드수가 65535개로 제한이 되어 있다.)

http://monaca.mobi/en/blog/crosswalk-performance/ 는 HTML5에 대한 호환성을 Crosswalk와 기본 웹뷰 사이에 수치를 비교한것인데, 일반 웹뷰는 278, 크로스워크는 호환성 부분에서 493 포인트를 낸것을 확인할 수 있다.


게임이나 일반 애플리케이션의 경우는 네이티브 앱 개발이 아무래도 대세가 되겠지만, SNS나 미디어성 컨텐츠는 개발의 용이성과 호환성 그리고 다양한 컨텐츠 포맷을 지원하는 점에서 HTML5 기술이 지원되는 웹뷰는 아무래도 필수적인 기술이 되지 않을까 싶다.


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 김용찬 2016.09.29 04:55  댓글주소  수정/삭제  댓글쓰기

    잘봤습니다 감사해요

  2. 김은기 2017.06.26 14:57  댓글주소  수정/삭제  댓글쓰기

    잘봤습니다 감사합니다.

  3. 이용희 2018.08.29 16:10  댓글주소  수정/삭제  댓글쓰기

    좋은글 잘 봤습니다.