Google Cloud Storage (GCS) to elastyczne, wydajne i ekonomiczne rozwiązanie do przechowywania danych w chmurze. Często zdarza się, że chcemy przenieść dane między różnymi kubełkami GCS.
CLI
Najprostszym do tego narzędziem, będzie dostarczany przez Google gsutil.
Rozpocznijmy od stworzenia źródłowego kubełka:
$ gsutil mb gs://mz-blogasek-src
Skopiujmy do niego testowy plik
$ gsutil cp 100B.txt gs://mz-blogasek-src
A teraz utwórzmy docelowy kubełek:
$ gsutil mb gs://mz-blogasek-dest
Nic nie stoi teraz na przeszkodzie, żeby skopiować przykładowy plik z jednego do drugiego kubełka:
$ gsutil cp gs://mz-blogasek-src/100B.txt gs://mz-blogasek-dest
Jeżeli naszym celem jest zsynchronizowania całych zawartości kubełków, lepszym rozwiązaniem będzie wykonanie komendy
$ gsutil rsync gs://mz-blogasek-src gs://mz-blogasek-dest
Kotlin
Użycie dostarczonej przez Google aplikacji gsutil jest w większości przypadków całkowicie wystarczające. Jednak co, jeśli podczas kopiowania plików chcielibyśmy je dodatkowo przetworzyć? A co, jeśli chcielibyśmy zautomatyzować cały proces? W takim przypadku przychodzą z pomocą Google Cloud Functions.
Usługa ta umożliwia nam wdrażanie funkcji, które reagują na określone zdarzenia. W naszym przypadku będzie to zdarzenie google.cloud.storage.object.v1.finalized, które występuje w określonym przez nas kubełku.
Funkcja napisana z wykorzystaniem Google Cloud SDK jest klasą, która implementuje CloudEventsFunction.
Interfejs ten posiada jedną metodę:
void accept(CloudEvent event) throws Exception;
Jej przykładowa implementacja, potrafiąca wydobyć szczegółowe informacje, niezbędne do wykonania interesujących nas operacji kopiowania i kompresji, może wyglądać jak poniżej:
override fun accept(event: CloudEvent?) {
val storageObjectDataBuilder = StorageObjectData.newBuilder()
JsonFormat.parser().merge(
event?.data?.toBytes()?.decodeToString(),
storageObjectDataBuilder)
val storageObjectData = storageObjectDataBuilder.build()
}
Obiekt storageObjectData posiada wiele pól, w tym:
-
bucket – zawierający nazwę kubełka, do którego odnosi się zdarzenie,
-
name – z nazwą pliku, który pojawił się lub został zmieniony.
Dysponując tymi informacjami, możemy teraz napisać potrzebny kod. W języku Kotlin, korzystając z biblioteki Apache Commons Compress, może on wyglądać jak poniżej:
private fun compress(sourceBucketName: String, sourceFileName: String) {
Ustalamy docelowy kubełek, oraz nazwę pliku wynikowego:
val destinationBucketName = System.getenv(DESTINATION_BUCKET_ENV)
val destinationFileName = "${sourceFileName}.gz"
Tworzymy identyfikatory źródłowego oraz docelowego pliku:
val sourceBlobId = BlobId.of(sourceBucketName, sourceFileName) val destinationBlobId = BlobId.of(destinationBucketName, destinationFileName)
Wykonujemy kopiowanie i kompresowanie właściwe:
Channels.newInputStream(storage.reader(sourceBlobId)).use { inputStream ->
Channels.newOutputStream(
storage.writer(
BlobInfo.newBuilder(destinationBlobId).build())).use { outputStream ->
GzipCompressorOutputStream(outputStream).use { gzipOutputStream ->
val buffer = ByteArray(1024)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
gzipOutputStream.write(buffer, 0, bytesRead)
}
}
}
}
Po napisaniu funkcji, pozostaje już tylko jej wdrożenie na Google Cloud Platform (GCP). Można to zrobić, korzystając z linii poleceń:
$ gcloud functions deploy replicator_function
--gen2
--source=build/libs/
--entry-point com.zamolski.storagereplicator.ReplicatorFunction
--env-vars-file env-vars.yaml
--runtime java17
--memory=256MB
--max-instances=5
--region europe-central2
--trigger-event-filters="type=google.cloud.storage.object.v1.finalized"
--trigger-event-filters="bucket=mz-blogasek-src"
Kompletny kod dla powyższego przykładu znajduje się tutaj.