Overlayfs에 대한 간단한 삽질

서론

셋업 후 한 동안 유지보수를 해주지 않았던 나스를 최근 열심히 유지보수 하고 있습니다. 오래된 도커 이미지들을 다시 빌드해서 최신 이미지로 업데이트 하고, 새로운 서비스도 설치 과정에 있습니다.

유지보수 과정 중에 진행한 또 다른 작업은 바로 새 하드를 끼우는 것입니다. 예전부터 새 하드를 끼우면 용량 확장을 어떻게 하지에 대한 고민을 많이 했었는데, 고민의 이유는 다음과 같습니다.

  1. RAID 1/5/6 을 사용할만큼의 재정적 여유가 없음
    현재 나스 프로젝트의 구성원이 전부 학생인지라, 나스를 사용하기 위해 하드를 한 번에 여러개 구매한다고 했을 때 재정적인 문제로 구성원의 찬성을 얻기가 힘듭니다.
  2. 이전 하드를 읽기 전용으로 유지하고 싶음
    이건 제 개인적인 바람인데, 백업의 편의성이나 기타 이유로 한번 끝까지 사용한 하드는 더 이상의 내용 수정을 하고 싶지 않습니다.
  3. 각 하드디스크는 따로 떼어놓았을 때도 유효한 파일시스템이었으면 좋겠음
    이 또한 제 개인적인 바람으로, rsync 등으로 백업하기 편했으면 좋겠기 때문입니다.

사실 1번 이유가 제일 중요했고, 2번과 3번 이유는 그냥 그러면 좋고 아니면 말고 수준이긴 했습니다. 어찌됐든, RAID 0이나 JBOD 같은 걸 사용하기에는 레이드 어레이가 깨졌을 때가 걱정이 되었습니다. 그래서 그런 선택지들을 제외하고 2가지 방법을 생각해냈습니다.

  1. 각 하드디스크별로 폴더를 분리 (/HDD1, /HDD2 의 경로로 접근하게)
  2. 전체 하드디스크를 유니온 마운팅

1번 방식은 폴더 관리가 힘들다는 이유로 기각당해서 2번으로 하기로 결정하였습니다.

유니온 마운팅

유니온 마운팅의 장점은 저의 니즈를 전부 충족해줄 수 있다는 것이었고, 단점은 우선 속도가 느리고 완전히 POSIX-Compliant 하지는 않다는 것입니다. 하지만 생각보다 속도의 차이가 얼마 나지 않았고, POSIX-Compliant 하지 않은 것도 정말 atime이 업데이트 되거나 하지 않는 거 빼면 거의 없었기 때문에 유니온 마운팅은 굉장히 매력적인 선택지였습니다.

유니온 마운팅의 대표적인 구현체에는 Aufs와 Overlayfs가 있습니다. Aufs는 우선 여러개의 writable branch를 지정해 어떤 순서로 쓸지도 설정할 수 있는 등 굉장히 세밀하게 옵션 설정이 가능합니다. 하지만 그 대신에 Overlayfs에 비해 살짝 성능이 뒤쳐진다는 말이 있습니다. 그리고 무엇보다도 현재 커널에 머지되어 있지 않은 것으로 알고 있습니다.

저의 사용 케이스는 Overlayfs로도 충분히 커버될만큼 복잡하지 않아서 Overlayfs를 사용했습니다.

Overlayfs

하지만 마지막까지 제가 Overlayfs 사용을 머뭇거리게 했던 것은 바로 위키백과의 한 문장이었습니다.

OverlayFS does not support renaming files without performing a full copy-up of the file; however, renaming directories in an upper filesystem has limited support.

과연 진짜로 그럴까? 하고 서버에서 테스트 해보니 정말로 파일 이름을 바꿀 때마다 전체 파일을 복사하는 현상이 일어났습니다.

현재 나스에는 주로 Makemkv로 리핑을 떠둔 블루레이라던가 라이브 스트리밍을 다운로드 받아놓은 것들 등등이 있습니다. 즉, 파일 하나하나가 굉장히 고용량이며, 혹시라도 폴더 정리를 하다가 실수로 이름을 바꾸면 그것들을 전부 복사하게 되는 것이었습니다.

따라서 Overlayfs에 Copy-Up 을 막는 옵션을 추가할 생각을 해보았습니다. 처음으로는 fuse-overlayfs 를 개조해보았습니다. 실제로 테스트는 해보지 않았지만, 다음과 같이 수정하면 되지 않을까 생각하였습니다.

--- a/main.c
+++ b/main.c

@@ -2892,5 +2892,7 @@
 static int
 copyup (struct ovl_data *lo, struct ovl_node *node)
 {
+  errno = EXDEV; 
+  return -1;
   int saved_errno;
   int ret = -1;

그러나 fuse를 이용하지 말고 그냥 커널 코드 자체에 옵션을 추가하는 것도 나쁘지 않을 것 같아서 커널에 있는 overlayfs 코드를 조금 보았습니다. 그래서 다음과 같이 수정하면 될 것 같았습니다.

--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -20,1 +20,2 @@
 	bool metacopy;
+ 	bool copyup;

--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -436,1 +436,3 @@
 	OPT_METACOPY_OFF,
+	OPT_COPYUP_ON,
+	OPT_COPYUP_OFF,
@@ -458,1 +460,3 @@	{OPT_METACOPY_OFF,		"metacopy=off"},
+	{OPT_COPYUP_ON,		"copyup=on"},
+	{OPT_COPYUP_OFF,	"copyup=off"},
@@ -611,1 +615,8 @@ 
+	case OPT_COPYUP_ON:
+		config->copyup = true;
+		break;
+
+	case OPT_COPYUP_OFF:
+		config->copyup = false;
+		break;

--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -934,1 +934,4 @@
 	int err = 0;
+	struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
+	if (!ofs->config.copyup)
+		return -EXDEV;

물론, 위 코드들은 전부 테스트 되지 않은 코드들입니다.

그리고 실제로 사용할 계획도 없어졌습니다. 그 이유는, 알고보니 파일 이름을 바꾸는 것은 Copy-Up을 필요로 하지 않았기 때문입니다.

Copy-Up 없는 rename

그러면 왜 서버에서 테스트 할 때는 Copy-Up이 일어났냐는 문제가 있는데, 그 이유는 간단히 제 서버의 커널 버전이 낮아서입니다. 실제로, 아치리눅스를 구동중인 제 노트북에서 테스트했을 때에는 Copy-Up 없이 복사가 되었습니다.

심지어 같은 디바이스일 때만 Copy-Up이 안 일어나는 것도 아니라서, 5MB짜리 루프 디바이스를 만들어 이를 upper layer로 설정하고 30MB짜리 파일 이름을 바꿔도 아무런 문제도 일어나지 않았습니다.

debugfs 를 사용해보면 정확하게 어떤 일이 일어나는지 이해하기 쉬운데, 최신 버전에서는 디렉토리 이름을 바꾸는 것 처럼 파일 이름을 바꿀 때에도 xattr을 설정해서 redirect로 처리하는 듯 합니다.

이걸 서버에서 쓰려면 당연히 서버의 커널을 업데이트 해야만 했고, Ubuntu 20.04로 업데이트 한 결과 서버에서도 파일 이름을 바꿀 때 Copy-Up이 일어나지 않았습니다.

결론

그래서 좀 삽질을 하긴 했는데, 결론만 말하면 다음과 같습니다.

최신 커널에 있는 Overlayfs에서는이름 바꿀 때에도 Copy-Up이 일어나지 않습니다.