Azure Blob Storage를 사용해서 정적인 웹 사이트 배포

아주 간단한 웹사이트가 필요할 때가 있다.

로그인도 필요없고 복잡한 기능이 없는, 문서를 공유하자던 웹의 본질에 가까운 그런 사이트가 필요할 때가 있다. 회사소개 홈페이지, 정식 서비스 오픈전에 공사중임을 표시하는 랜딩 페이지,  웹에서 작동되는 게임이 올라간 웹 페이지 등 그런 사례는 많다. 이런 웹사이트의 대걔 html, js, css, 이미지 파일 몇 개로  구성되고 서버측 개발이 필요없다. 이 사이트를 운영하기 위해서 가상컴퓨터나 Azure 웹앱도 부담스럽다. 이럴 때 Azure blob storage에 컨텐츠를 복사해서 쉽게 웹 사이트를 운영할 수 있다. Azure Blob Storage에 http 서버가 내장되어 있기 때문이다.

이 글은 Azure blob storage 정적인 웹사이트를 운영하는 방법과 수정이 되었을 때 배포, 성능향상을 위한 최적화에 대한 내용이다.

  • gulp 와 여러 gulp 플러그인을 이용해서 html / js/ css를 minify 하고 gzip으로 압축하여 최적화
  • html / js / css / image 를 Azure Blob Storage에 Dev 환경과 Production 환경에 각각 배포

이글과 관련된 소스코드는 Github ilseokoh/jwplayertest에 공개되어 있다.

사전준비

  • package.json 파일을 만들고 Gulp 및 Gulp 플러그인 설치
> npm --version
3.10.10

> npm init

name: (oproject)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to C:\src\oproject\package.json:
{
  "name": "oproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

> npm install gulp -g
  • 필요한 gulp 플러그인을 아래 명령으로 설치한다. 개발환경에서만 필요하기 때문에 –save-dev 옵션을 붙여준다.
> npm install gulp-clean-css gulp-concat gulp-deploy-azure-cdn gulp-livereload gulp-minify-html gulp-uglify gulp-util gulp-webserver --save-dev
gulp-deploy-azure-cdn Azure Blob 스토리지로 컨텐츠를 업로드. gzip 압축과 http header 설정 기능도 포함되어 있다.
gulp-clean-css CSS를 minify(공백,코멘트를 제거하여 용량을 줄임)
gulp-minify-html HTML을 minify(공백, 코멘트를 제거해서 용량을 줄이는 방법)해준다.
gulp-uglify javascript 파일을 uglify(공백, 코멘트등을 제거해서 용량을 줄이는 방법)해준다.
gulp-util gulp util 여기서는 dev / production 환경을 스위치 하는데 썼다.
gulp-concat string 붙이는 플러그인
gulp-webserver 로컬 테스트용 웹서버. http://localhost:8000으로 접근해서 디버깅 용도
gulp-livereload 로컬에서 테스트할 때 파일이 변경되면 다시 빌드해주는 플러그인

 

최종 package.json 파일

{
  "name": "o-project",
  "version": "1.0.0",
  "description": "O Project",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ilseokoh/jwplayertest.git"
  },
  "author": "kevin",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/ilseokoh/jwplayertest/issues"
  },
  "homepage": "https://github.com/ilseokoh/jwplayertest#readme",
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-clean-css": "^3.5.0",
    "gulp-concat": "^2.6.1",
    "gulp-deploy-azure-cdn": "^2.0.0",
    "gulp-livereload": "^3.8.1",
    "gulp-minify-html": "^1.0.6",
    "gulp-uglify": "^3.0.0",
    "gulp-util": "^3.0.8",
    "gulp-webserver": "^0.9.1"
  }
}

Gulp 파일 만들기

이제 gulpfile.js 파일을 만들어서 gulp를 이용해서 하나씩 작업을 이어 나가보자. 먼저 텍스트 에디터를 사용해서 gulpfile.js 파일을 만든다.

1. 플러그인 로드와 설정

gulpfile.js 상단에 플러그인을 require 문을 이용해서 로딩하고 config 오브젝트에 필요한 설정값들을 준비한다. config에는 소스파일들의 종류에 따른 위치, 결과물이 담길 dist 폴더 그리고 Azure Blob 스토리지의 이름과 키를 설정해준다.

var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var minifyhtml = require('gulp-minify-html');
var cleanCSS = require('gulp-clean-css');
var webserver = require('gulp-webserver');
var livereload = require('gulp-livereload');
var util = require('gulp-util');
var deployCdn = require('gulp-deploy-azure-cdn');

console.log('Production build? : ' + util.env.production);

var config = {
	js: 'src/js/**/*.js',
	css: 'src/css/**/*.css',
	img: 'src/img/*.*',
	html: 'src/*.html',
	dist: 'dist/',
	production: !!util.env.production,
	azureStorageAccountName: 'blobwebapp1',
	azureStorageKey: 'uS7lkWaTb+515uR6MsruXWDPJJ4UNxoNJ4Wu8oPZ8O+w4V0CffnsxEW0RhKQ=='
};

2. HTML, Javascript, css 최적화 및 이미지 복사

Cloud 서비스는 대부분 outbound 트래픽에 과금을 한다. CDN도 마찬가지로 전송된 만큼 과금한다. 따라서 전송 용량을 줄이면 비용이 줄어든다. 아주 작은 용량이라도 오랫동안 절약하면 절약된 비용은 생각보다 크다. 클라우드는 항상 아껴서 사용해야 한다. 또한 용량이 작으면 빠르다. 사용자가 조금이라도 쾌적하게 사이트를 사용할 수 있도록 최적화 해줘야 한다. 그 첫번째 방법이 minify 다.텍스트 기반의 파일들은 모두 minify를 할 수 있다. minify는 공백, 주석 등을 제거하고 js 파일의 경우 긴 변수 이름도 짧게 변경하는 방법등으로 용량을 줄이는 방법이다.

또한 이미지나 동영상에 비해 작은 javascript, CSS 파일의 경우 여러개로 구성되어 있을 경우 여러 번의 HTTP connection을 맺고 끊는 시간 때문에 속도가 느려진다. 따라서 가능하다면 javascript 파일을 하나로 합쳐서 효과를 볼 수 있다.

이 작업을 gulp의 몇 가지 플러그인을 이용해서 할 수 있다. gulp의 태스크를 만들고 pipe로 이어서 명령을 만들어준다.

여기서 config.production 가 true 일때만 minify 최적화를 실행하는데 이건 gulp 명령어의 옵션으로 production과 dev를 구분해준다. > gulp –production 명령은 config.production을 true로 만들어서 minify를 수행한다. 이렇게 하는 이유는 dev 환경에서는 디버깅을 쉽게 하기 위해서 minify를 하지 않는 소스를 사용하는 것이 유리하기 때문이다.

// js 파일을 합치고 uglify, gzip
// 소스 폴더의 모든 js 파일을 main.js 하나로 합치고 uglify()로 최적화 한다. 
gulp.task('combine-js', function () {
	return gulp.src(config.js)
		.pipe(concat('main.js'))
		.pipe(config.production ? uglify() : util.noop())  // production 일때만
		.pipe(gulp.dest(config.dist + 'js/'));
});

// HTML 파일 압축 , gzip
gulp.task('compress-html', function () {
	return gulp.src(config.html)
		.pipe(config.production ? minifyhtml() : util.noop())
		.pipe(gulp.dest(config.dist));
});

// css minify, gzip
gulp.task('minify-css', () => {
  return gulp.src(config.css)
    .pipe(config.production ? cleanCSS({compatibility: 'ie8'}) : util.noop())
    .pipe(gulp.dest(config.dist + 'css/'));
});

// img 폴더 복사. 단순히 복사만 한다. 
gulp.task('copy-img', function () {
	return gulp.src(config.img)
		.pipe(gulp.dest(config.dist + 'img/'));
});

// jwplayer 폴더 복사. 외부에서 가져온 소스와 관련 파일은 만지지 않고 그냥 복사만 했다. 
gulp.task('copy-jwplayer', function () {
	return gulp.src('src/jwplayer/**/*')
		.pipe(gulp.dest(config.dist + 'jwplayer/'));
});

gulp.task('build', ['combine-js','compress-html','copy-img','minify-css', 'copy-jwplayer']);

 

마지막 라인에서는 build 라는 태스크를 만들고 위에서 정의한 4가지 태스크를 한번에 수행하는 명령을 만들었다. 이 build 태스크로 지금까지 만든 최적화 코드를 실행할 수 있다. command line 에서 다음과 같이 명령하면 4가지 작업이 수행된다. 결과는 dist 폴더에 생성된다. –production 옵션을 붙였다 떼었다 하면서 minify가 수행되는지도 확인해본다.

> gulp build
> gulp build --production

3. 로컬 개발환경 설정

아무리 간단하고 정적인 코드라고 해도 디버깅이 필요하고 테스트가 필요하기 때문에 gulp-webserver를 사용해서 환경을 만들어준다. 그리고 default 작업을 만들어서 gulp 명령으로 실행되도록 한다. > gulp 명령을 실행하면 build 가 실행되고 watch와 server도 실행된다. 웹 브라우저에서 http://localhost:8000 으로 접속한다. 그러면 index.html이 표시된다. 이 상태에서 소스를 수정하고 웹 브라우저에서 확인하면서 개발을 진행하면 된다.

// 개발용 웹서버 실행 localhost:8000 
gulp.task('server', function () {
	return gulp.src(config.dist)
		.pipe(webserver());
});

// 변경 감지 및 업데이트 
gulp.task('watch', function () {
	livereload.listen();
	gulp.watch(config.js, ['combine-js']);
	gulp.watch(config.html, ['compress-html']);
	gulp.watch(config.img, ['copy-img']);
	gulp.watch(config.css, ['minify-css']);
	gulp.watch('dist/**').on('change', livereload.changed);
});

gulp.task('default', ['build','watch','server']);

4. Azure Blob 스토리지로 업로드

Azure Blob 스토리지로 배포를 할 때 Dev 사이트와 Prod(production) 사이트를 구분해서 진행하고 Prod 사이트에는 CDN을 붙여서 라이브 서비스에 활용하고 Dev 사이트는 개발 및 테스트용도록 사용하도록 해보자. 간단한 사이트지만 나름 DevOps 환경을 만들고 향후에 Visual Studio Team Services 같은 협업 툴을 이용하여 CI (Continuous Integration), CD (Continuous Deployment) 까지 설정 가능하다. Azure Storage Account를 만들고 Blob에 dev와 prod라는 이름으로 Container 두 개를 만든다.  두 개 모두 액세스 형식을 Blob 으로 오픈해준다.

업로드 중에 한가지 더 최적화를 해줄 수 있다. HTTP 압축을 이용해서 용량을 더 줄여보자. Azure Blob 스토리지로 올릴 때 텍스트 기반의 파일들 (HTML, Javascript, CSS)은 gzip으로 압축을 해주고 이미지나 동영상 같은 파일들은 이미 압축이 되어 있기 때문에 그냥 업로드 하는 방법을 쓰면된다. 다행히 gulp-deploy-azure-cdn 플러그인에 그 기능이 들어있다.  zip: true 옵션을 사용하면 컨텐츠를 gzip으로 압축하고 contentEncoding 값을 gzip으로 설정해서 업로드 한다. 이렇게 되면 HTTP 헤더에 contentEncoding: gzip 값이 설정되어 웹브라우저에서 올바로 컨텐츠를 표시할 수 있다. 전송용량이 또 한번 줄어들기 때문에 비용과 속도 면에서 효과를 볼 수 있다.

// css, js, html파일을 제외한 파일들을 gzip하지 않고 Azure blog에 업로드
gulp.task('dev-without-gzip', function () { 
	return gulp.src(config.dist + "**/!(*.css|*.js|*.html)")
			.pipe(deployCdn({
                containerName: 'dev',
                serviceOptions: [config.azureStorageAccountName, config.azureStorageKey],
                folder:  '',
                zip: false,
				concurrentUploadThreads: 10,
			}));
});

// css, js, html 파일은 gzip으로 압축하고 contentEncoding을 gzip으로 설정하고 업로드
gulp.task('dev-gzip', function () { 
	return gulp.src(config.dist + "**/*.{css,js,html}")
			.pipe(deployCdn({
                containerName: 'dev',
                serviceOptions: [config.azureStorageAccountName, config.azureStorageKey],
                folder:  '',
                zip: true,
				concurrentUploadThreads: 10,
			}));
});

gulp.task('prod-without-gzip', function () { 
	return gulp.src(config.dist + '**/!(*.css|*.js|*.html)')
			.pipe(deployCdn({
                containerName: 'prod',
                serviceOptions: [config.azureStorageAccountName, config.azureStorageKey],
                folder:  '',
                zip: false,
				concurrentUploadThreads: 10,
			}));
});

gulp.task('prod-gzip', function () { 
	return gulp.src(config.dist + '**/*.{css,js,html}')
			.pipe(deployCdn({
                containerName: 'prod',
                serviceOptions: [config.azureStorageAccountName, config.azureStorageKey],
                folder:  '',
                zip: true,
				concurrentUploadThreads: 10,
			}));
});

gulp.task('deploy-dev', ['build','dev-gzip', 'dev-without-gzip']);
gulp.task('deploy-prod', ['build','prod-gzip', 'prod-without-gzip']);

이제 아래 명령으로 배포를 실행한다. prod 배포는 –production 스위치를 붙여준다.

> gulp deploy-dev
> gulp deploy-prod --production

여기까지 gulpfile.js에 대한 설명이고 전체 소스코드는 github에서 살펴볼 수 있다.

최종 결과물을 보려면 Blob URL (예를들어 https://blobwebapp1.blob.core.windows.net/prod/index.html) 로 접속해서 결과물을 확인할 수 있다.