Linux üzerinde Java ile bir Postgresql veritabanını günlük Azure 'a yedeklemek

Bu yazıda blogum için kullandığım postgresql veritabanını java ile linux üzerinde düzenli olarak backup alıp, Azure File Share 'da nasıl sakladığımı yazdım. Ayrıca yapılan işlem ile ilgili bir bilgi maili de atabiliyorum. Aslında bulut sistemleri zaten veritabanlarını otomatik yedekleyebiliyor ve hatta restore edebiliyor. Fakat çeşitli sebeplerden dolayı ayrı bir veritabanı sistemine ücret ödemek istemeyebilirsiniz. Bu durumda bir VPS yani Virtual Private Server yani kiralık sunucu kullanıyorsanız içerisinde kendi veritabanı sistemini kurgulamanız da mümkün. Tabi sanal sunucunuza bir zarar geldiğinde veritabanınızı da kaybetme riski bulunuyor. Bu tam da benim karşılaştığım durum :)

Her ne kadar spesifik bir case olsa da bazı şeyleri kendi çözümlerim ile halletmeyi seviyorum. Bu blog sitesi için basit bir veritabanı kullanıyorum, o yüzden ayrıca veritabanı ücreti ödemek istemedim. Tabi bu duruma düşmemek için yine bulut tabanlı başka çözümler mutlaka mevcuttur. Ama bir yandan da cronjob ve postgresql CLI üzerine birşeyler de öğrenmiş oldum. Bu iş için 5 adımdan oluşan bir planımız var.

  • Java ile postgresql veritabanını yedekleyen komutu çalıştırmak
  • Microsoft azure üzerinde File Share hizmetini hazırlamak
  • Oluşan dosyayı Azure File Share 'a yüklemek
  • İşlem başarılı ise gmail üzerinden bilgilendirme maili göndermek
  • Bu java uygulamasını linux 'da Cron job şeklinde çalışacak hale getirmek

Postgresql backup kodları

Bir postgresql veritabanının command line ile backup 'ını almak ve dosyaya yazdırmak mümkün. Bunu PgAdmin kurduktan sonra pg_dump ile yapıyorsunuz. Windows veya linux 'da olsa fark etmiyor, aynı komut geçerli. Fakat kodlar biraz değişebiliyor. O yüzden 2 farklı metodu yazacağım. Aradaki comment 'ler ile açıklamaları da yazdım.

			
public int exportDbLinux(String filePath)
{
	// sonuç kodunun 0 olması gerekecek
	int exit = -1;
	// java.lang.Process sınıfı
	Process p;
	try
	{
		p = Runtime.getRuntime().exec(new String[]
		// aşağıdaki parametreler veritabanının tablolar ve veri ile beraber yedeklenmesini sağlıyor, hatta sequence 'ları ve değerlerini de
		// schema adı "blogsema" ve veritabanı adı "blog", postgresql 'de veritabanları altında şemalar bulunur çünkü
		{ "pg_dump", "--format", "p", "--blobs", "--file", filePath, "--create", "--inserts", "--column-inserts", "--no-comments", "--encoding", "UTF8", "--schema", "blogsema", "blog" });
		// bu kod hata process 'te bilgisi varsa alıyor
		final BufferedReader r = new BufferedReader(new InputStreamReader(p.getErrorStream()));
		String line = r.readLine();
		StringBuilder builder = new StringBuilder();
		while (line != null)
		{
			line = r.readLine();
			builder.append(line + "\n");
		}
		if (!builder.toString().equals(""))
		{
			// eğer işlem sırasında hata oluşmuş ise aşağıda yazacağım mail gönderme işlemi ile hata bildiriyor
			sendMail("Process error stream = " + builder.toString(), false);
		}
		r.close();
		p.waitFor();
		// process 'in sonuçlanması ile dönen kod, 0 değil ise başarısızdır
		exit = p.exitValue();

	}
	catch (IOException | InterruptedException e)
	{
		// hata alırsam yine mail gönderebilirim
		sendMail("Postgrexc = " + e.getMessage(), false);
	}
	return exit;
}
			
		

Burada önemli bir not var. Linux 'da nedense bu process 'e veritabanı şifresini veremedim. Bunun için bir environment variable veya özel bir işlem gerekiyor sanırım. Siz şifreyi de set etmek isterseniz bu örnekten yardım alabilirsiniz. Aşağıdaki metod ise windows işletim sistemi üzerinde kullanabileceğiniz metod. Bunda şifre içinde var.

			
public int exportDbWindows(String filePath)
{
	// sonuç kodunun 0 olması gerekecek
	int exit = -1;
	// java.lang.Process sınıfı
	Process p;
	ProcessBuilder pb;
	// aşağıdaki parametreler veritabanının tablolar ve veri ile beraber yedeklenmesini sağlıyor, ayrıca sequence 'ları ve değerlerini de
	// aşağıda sunucu_ip ve sunucu_port yazmanız gerekir, port değiştirmedi iseniz 5432 'dir
	// ip olarak emin olmamakla beraber 127.0.0.1 çalışmayabilir
	// PgAdmin'in nerede kurulu olduğuna da dikkat etmeniz gerekebilir
	// no-password ile şifre gerekmeden bağlanıyoruz ama şifre yine de aşağıda yazıyor
	// "blogsema" şema ismi ve "blog" veritabanını yedekliyoruz, bunları değiştiriniz eğer farklı ise
	// postgresql 'de veritabanları altında şemalar bulunur
	// postgresuser ise kullanıcı adı, siz de kendi user 'ınızı yazınız
	String pg_dump_path = "C:\\Users\\numan\\AppData\\Local\\Programs\\pgAdmin 4\\v6\\runtime\\pg_dump.exe";
	pb = new ProcessBuilder(pg_dump_path, "--host", "<sunucu_ip>", "--port", "<sunucu_port>", "--username", "postgresuser", "--no-password", "--format", "p", "--blobs", "--file", filePath, "--create", "--inserts", "--column-inserts", "--no-comments", "--encoding", "UTF8", "--schema", "blogsema", "blog");
	try
	{
		final Map<String, String> env = pb.environment();
		// şifreniz kısmı postgresuser kullanıcısının şifresidir
		env.put("PGPASSWORD", "<şifreniz>");
		p = pb.start();
		// process 'i başlat ve hata varsa error stream 'i al
		final BufferedReader r = new BufferedReader(new InputStreamReader(p.getErrorStream()));
		String line = r.readLine();
		StringBuilder builder = new StringBuilder();
		while (line != null)
		{
			line = r.readLine();
			builder.append(line + "\n");
		}
		if (!builder.toString().equals(""))
		{
			// hata oluşmuş ise mail gönderme metodu ile mail gönder
			sendMail("Process error stream = " + builder.toString(), false);
		}
		r.close();
		p.waitFor();
		// process 'in sonuç kodu 0 olmalı, aksi durumda hata oluşmuştur.
		exit = p.exitValue();

	}
	catch (IOException | InterruptedException e)
	{
		// hata oluşmuş ise mail gönderme metodu ile mail gönder
		sendMail("Postgrexc = " + e.getMessage(), false);
	}
	return exit;
}
			
		

Peki bu metodları nasıl çalıştırıyorum?

			
// windows için yedeklenecek text dosyası adı
// String filePath = "C:\\Users\\<kullanıcınız>\\Desktop\\blogbackup.sql";
// linux için yedeklenecek text dosyası adı
// String filePath = "/home/<kullanıcınız>/Desktop/blogBackup.sql";
// bu dosya notepad ile açılıp görülebilen sql dosyasıdır
// dilerseniz okunamayan ziplenmiş formatta da backup alınabilir fakat onun parametreleri farklı olur
String res = "";
if (exportDbLinux(filePath) == 0)
{
	res = uploadFile(filePath);
}
if ("".equals(res) || res == null)
{
	// hata oluşması durumu
	sendMail("RES = null", false);
}
else
{
	// hata yok ise dosya boyutunu KB türünden içeriğe koyup mail atıyor olacağım
	// bu sayede birşeyler ters giderse dosya boyutundan anlayabilirim
	// mesela veritabanının benden habersiz uçmuş olması gibi
	sendMail("Blog backed up " + ((double) new File(filePath).length() / 1024.0), true);
}
			
		

Backup dosyasını oluşturan metodumuz tamam. Sıra Azure üzerinde File Share özelliğini açmak.

Azure File Share konfigürasyonu

Öncelikle Microsoft Azure üzerinde bir hesap oluşturmanız gerekiyor. Bu adımları burada yazamam, fakat bir subscription, ödeme yöntemi ve Storage account (resource) ayarlamanız gerekiyor. Storage account oluşturduktan sonra içerisindeki File Share kısmını ekran görüntüleri ile ekleyeceğim aşağıda. Sonrasında da dosyayı yükleyen kodları yazdım.

Storage account 'a girdikten sonra File Shares ekranında + işareti ile File Share oluşturabilirsiniz. Aşağıdaki ekrandaki gibi isim verip, ikinci ekranda backup özelliğini de belirtebilirsiniz. Next ile devam edin ve kaydedin.

Oluşturduktan sonra dbbackup isimli File Share listede görünecektir. Benim diğer File Share 'larımı da görüyorsunuz listede. Tıklayıp içine girin.

Aşağıdaki ekranda ise Share Url bilgisini kopyalayıp saklayın. Burada dikkat, wsstorage benim storage account ismim, sizinki farklı olacaktır.

Sıradaki ekranda storage account 'unuz için bağlantı bilgileri olacak. Bu sayfada 2 adet anahtar oluşturabilirsiniz. Ben önceden oluşturduğum için hazır duruyor. İstediğiniz anahtar için Connection String bilgisini kopyalayın ve saklayın.

Azure File Share 'a java ile bağlanmak

Azure tarafı bu kadardı. Şimdi sıra java ile azure 'a bağlanmaya geldi. Bunun için maven projesi oluşturduysanız aşağıdaki dependency 'leri kullanabilirsiniz.

			
<!-- https://mvnrepository.com/artifact/com.azure/azure-identity -->
<dependency>
	<groupId>com.azure</groupId>
	<artifactId>azure-identity</artifactId>
	<version>1.3.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.azure/azure-storage-file-share -->
<dependency>
	<groupId>com.azure</groupId>
	<artifactId>azure-storage-file-share</artifactId>
	<version>12.12.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.azure/azure-core -->
<dependency>
	<groupId>com.azure</groupId>
	<artifactId>azure-core</artifactId>
	<version>1.26.0</version>
</dependency>
<dependency>
	<groupId>javax.mail</groupId>
	<artifactId>mail</artifactId>
	<version>1.4.7</version>
</dependency>
			
		

Java tarafında da yazacağımız metod aşağıdaki gibi. Açıklamalara dikkat.

			
private String uploadFile(String filePath)
{
	String storageConnectionString = "<connection_string_bilgisi>";
	String shareURL = "<share_url_bilgisi>";
	ShareClient shareClient = new ShareClientBuilder().endpoint(shareURL).connectionString(storageConnectionString).buildClient();
	PagedIterable<ShareFileItem> items = shareClient.getRootDirectoryClient().listFilesAndDirectories();
	// burada küçük bir algortimam var
	// dosya isimleri ile sonuna numara koyarak kaydediyorum ve en son 25 günlük backup 'ı tutuyorum
	// bu durumda backup alınamadığını 1 hafta geç fark etsem bile eski bir backup 'a dönebiliyorum
	// yani her gün 1 backup alıyorum ama üzerine yazmıyorum, en yenisini ekleyip eskisini delete yapıyorum
	// fakat öncelikle 25 tane dosyayı manuel olarak Azure 'a ekledim, bu algoritma kaç dosya saklandığını bilmiyor
	// azure 'un backup özelliğini de kullanabilirdim tabi ki
	int smallest = Integer.MAX_VALUE, biggest = 0;
	for (ShareFileItem shareFileItem : items)
	{
		int backupNumber = Integer.parseInt(shareFileItem.getName().substring(6));
		if (backupNumber < smallest)
		{
			smallest = backupNumber;
		}
		if (biggest < backupNumber)
		{
			biggest = backupNumber;
		}
	}
	biggest = biggest + 1;
	// en eskiyi delete yap
	shareClient.getFileClient("backup" + smallest).delete();
	// en yeniyi yükle, ama önce dosya oluştur
	shareClient.createFile("backup" + biggest, new File(filePath).length());
	shareClient.getFileClient("backup" + biggest).uploadFromFile(filePath);
	// exception yok ise yüklediğim dosya bilgisini döndür, emin olmak için
	return shareClient.getFileClient("backup" + biggest).getFilePath();
}
			
		

Sonucu gmail ile bildirmek

Bu işlem linux 'da her gece arka planda çalışacağı için kontrol etmem gerekmesin diye kendime mail attırıyorum. Bunun için dependency 'lerde javax.mail var. Mail gönderen metodumuz aşağıdaki gibi.

			
private void sendMail(String mailcontent, boolean success)
{
	// gmail ile mail göndermek için gereken ayarlar aşağıdaki gibi
	// outlook veya başka bir sunucu için farklı olabilir
	Properties props = new Properties();
	props.put("mail.smtp.host", "smtp.gmail.com");
	props.put("mail.smtp.socketFactory.port", "587");
	props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
	props.put("mail.smtp.ssl.trust", "smtp.gmail.com");
	props.put("mail.smtp.auth", "true");
	props.put("mail.smtp.starttls.required", "true");
	props.put("mail.smtp.ssl.protocols", "TLSv1.2");
	props.put("mail.smtp.starttls.enable", "true");
	props.put("mail.smtp.port", "587");
	Session session = Session.getDefaultInstance(props, new javax.mail.Authenticator()
	{
		@Override
		protected javax.mail.PasswordAuthentication getPasswordAuthentication()
		{
			// DIKKAT !!! buradaki gerçek gmail şifreniz değil
			// 2 faktörlü doğrulamayı etkinleştirmeniz gerekiyor Gmail üzerinden
			// "gmail third party access" şeklinde arama yaparak mail ayarlarınızı yapmanız gerekir
			return new javax.mail.PasswordAuthentication("<email_adresiniz>", "<şifreniz>");
		}
	});
	try
	{
		MimeMessage message = new MimeMessage(session);
		// burada Numank sadece gönderici kısmında isim görünmesi için
		message.setFrom(new InternetAddress("<email_adresiniz>", "Numank", "utf-8"));
		// alıcı_email kime mail göndermek istiyorsanız o adres, kendi kendinize gönderebilirsiniz
		message.setRecipients(RecipientType.TO, "<alıcı_email>");
		// başarılı veya başarısız mesajı da parametre ile burada
		message.setSubject(success ? "Backed up successfully" : "Backup failed", "utf-8");
		message.setContent("<html><body><h2>" + mailcontent + "</h2></body></html>", "text/html; charset=utf-8");
		javax.mail.Transport.send(message);
	}
	catch (Exception e)
	{
		// hata durumunu pek düşünmedim, hiç hata olmadı :)
		System.err.println("Mail failed " + mailcontent + "\n" + e.getMessage());
	}
}
			
		

Linux 'da java uygulamasını Cron Job haline getirmek

Sonunda kodlarımız hazır. Bu kodları bir main metoda aktarıp basit bir java sınıfı yazabilirsiniz. Dosyanın adı fark etmemekle beraber DBackup.jar oluştu diyelim. Bu jar dosyasında eğer Maven kullanıyorsanız, yukarıdaki dependency 'lerde belirttiğim kütüphanelerin de bir yerlerde olması gerekiyor. Aksi takdirde eğer path ile özellikle belirtmediyseniz (ki maven kullanıyorsanız böyledir muhtemelen) class kendi başına çalışmaya çalışacağından bu kütüphaneler load edilemeyebilir. Bunun için benim çözümüm Fat-Jar oluşturmak oldu. Yani dependency 'leri de içeren jar dosyası oluşturdum. Bunu da maven assembly plugin ile yaptım. Pom dosyasında build kısmında aşağıdaki gibi ekleyebilirsiniz.

			
<build>
	<finalName>DBackup</finalName>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-assembly-plugin</artifactId>
			<version>3.3.0</version>
			<configuration>
				<descriptorRefs>
					<descriptorRef>jar-with-dependencies</descriptorRef>
				</descriptorRefs>
			</configuration>
			<executions>
				<execution>
					<id>make-assembly</id>
					<phase>package</phase>
					<goals>
						<goal>single</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>
			
		

Böylece dosyayı mvn assembly komutu ile oluşturabilirsiniz. Sonra linux terminalinde crontab -e komutu ile (windows 'da zamanlanmış görevler yani scheduled tasks 'ın karşılığı) linux cron job 'larını görebilirsiniz. Hal-i hazırda görev olmadığı için bu liste boş görünecektir. Bu listeye 1 satır ekleyerek her gece saat 12 'de yedekleme dosyasını çalıştırmak isteyebilirsiniz. Aşağıdaki formattan yardım alabilirsiniz. MainClass main metodun bulunduğu class oluyor. "sizin_kullanici" ise linux kullanıcınız.

			
// minute hour day_of_month month day_of_week command_to_run
0 0 * * * java -cp /home/<sizin_kullanici>/Desktop/DBackup.jar MainClass
			
		

Daha sonra cron servisinin yani zamanlanmış görevlerin aktif olup olmadığını "systemctl status cron" komutu ile kontrol edebilirsiniz. Bildiğim kadarıyla Active görünüyor olması gerekir otomatik olarak.

İş basit kod büyük

Böylece dandik sayılabilecek bir iş için dolambaçlı bir sistem kurdum. Günün sonunda pg_dump 'ı öğrenmiş oldum ve java 'da process 'e bağladım. Azure File Share 'a java ile bağlanmış oldum. 25 günlük düzenli backup 'lar tutuyorum ve java ile mail atabiliyorum. Linux 'da bir işi nasıl düzenli olarak yapabileceğimi de görmüş oldum. Bu yetenekleri başka yerde de kullanabilmek gibi bir kazanımım oldu. Belki size de bir parçası lazım olur. Bir sonraki yazıda görüşmek üzere :)


Bir yorum yazabilirsiniz