نحوه Dockerize کردن یک برنامه Spring Boot با PostgreSQL
در این مقاله، نحوه استفاده از Docker با SpringBoot و PostgreSQL را به شما نشان خواهم داد، برای پیگیری باید درک اولیه ای از آنچه که وجود دارد داشته باشید. Docker
، Maven
نصب شده، تجربه با Spring Boot
و یک IDE به انتخاب شما.
اطلاع : اگر قبلاً یک برنامه Spring Boot دارید، می توانید بخش های 1 و 2 را رد کرده و مستقیماً به بخش 3 بروید و آن را دنبال کنید.
در اینجا می توانید کد و سایر منابع مورد استفاده در این نمایش را بیابید (github.com)
1 – چه خواهیم ساخت؟
برای اهداف آزمایشی، ما یک برنامه مدیریت دستور العمل ساده با دو نهاد ایجاد خواهیم کرد: Chef
و Recipe
.
2 – یک اپلیکیشن فنری بوت ایجاد کنید
برای این کار، در صورت استفاده میتوانید از افزونه Spring initializr یا Spring initializr استفاده کنید IntelliJ idea
. و وابستگی های زیر را انتخاب کنید: Spring Web
، PostgreSQL Driver
و البته Spring Data JPA
و حتما انتخاب کنید maven
به عنوان مدیر پروژه
پس از نصب پروژه، آن را استخراج کرده و با IDE مورد علاقه خود باز کنید.
همانطور که در بخش اول ذکر شد دو موجودیت ایجاد خواهیم کرد Chef
و Recipe
. به شرح زیر است:
@Entity
public class Chef {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "chef")
private List<Recipe> recipes;
public Chef() {
}
public Chef(Long id, String name) {
this.id = id;
this.name = name;
}
public Chef(String name) {
this.name = name;
}
// ADD GETTERS AND SETTER...
}
@Entity
public class Recipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 100)
private String name;
private String description;
@ManyToOne
@JsonBackReference
private Chef chef;
public Recipe() {
}
public Recipe(String name, String description) {
this.name = name;
this.description = description;
}
public Recipe(Long id, String name, String description) {
this.id = id;
this.name = name;
this.description = description;
}
// GETTERS AND SETTERS...
}
ما همچنین نیاز به ایجاد ChefController
، ChefService
، ChefRepository
و RecipeRepository
.
سرویس آشپز: سه روش اساسی دارد: createChefWithRecipes
که به عنوان پارامتر یک آشپز و لیستی از دستور العمل ها را در نظر می گیرد، getChefs
برای دریافت لیست سرآشپزها و در نهایت getNumberOfChefs
تعداد سرآشپزها را در پایگاه داده برمی گرداند.
@Service
public class ChefService {
private ChefRepository chefRepository;
private RecipeRepository recipeRepository;
public ChefService(ChefRepository chefRepository, RecipeRepository recipeRepository) {
this.chefRepository = chefRepository;
this.recipeRepository = recipeRepository;
}
@Transactional
public Chef createChefWithRecipes(Chef chef,List<Recipe> recipes){
recipes.stream().forEach(recipe -> recipe.setChef(chef));
this.chefRepository.save(chef);
this.recipeRepository.saveAll(recipes);
return chef;
}
public List<Chef> getChefs(){
System.out.println(this.chefRepository.findAll());
return this.chefRepository.findAll();
}
public Long getNumberOfChefs(){
return this.chefRepository.count();
}
}
رئیس کنترل: فقط یک روش دارد که لیستی از سرآشپزها را برمی گرداند
@RestController
@RequestMapping("/api/chefs")
public class ChefController {
private final ChefService chefService;
public ChefController(ChefService chefService) {
this.chefService = chefService;
}
@GetMapping
public List<Chef> getAllChefs(){
return chefService.getChefs();
}
}
و در نهایت، چند ردیف اولیه به پایگاه داده خود اضافه می کنیم (اختیاری)، برای این کار یک کلاس اولیه داده ایجاد می کنیم و متد run را از رابط فرمان اجرا می کنیم.
@Component
public class DataInitializer implements CommandLineRunner {
private final ChefService chefService;
public DataInitializer(ChefService chefService) {
this.chefService = chefService;
}
@Override
public void run(String... args) throws Exception {
if(chefService.getNumberOfChefs() > 1) {
System.out.println("Chefs already initialized!");
return;
}
Chef chef1 = new Chef("Gordon Ramsay");
Chef chef2 = new Chef("Jamie Oliver");
Chef chef3 = new Chef("Anthony Bourdain");
List<Recipe> chef1Recipes = Arrays.asList(
new Recipe("Beef Wellington", "A classic British..."),
new Recipe("Scrambled Eggs", "A simple breakfast..."),
new Recipe("Beef Burger", "A juicy burger made...")
);
List<Recipe> chef2Recipes = Arrays.asList(
new Recipe("Spaghetti Carbonara", "A creamy pasta...")
new Recipe("Roast Chicken", "A classic roastchicken"),
new Recipe("Fish and Chips", "A traditional...")
);
chefService.createChefWithRecipes(chef1,chef1Recipes);
chefService.createChefWithRecipes(chef2,chef2Recipes);
chefService.createChefWithRecipes(chef3,new ArrayList<>());
}
}
3 – برنامه را در یک فایل JAR بسته بندی کنید
به سادگی دستور را اجرا کنید mvn package
و بررسی کنید که یک فایل jar در زیر پوشه ایجاد شده باشد target
.
mvn package -DskipTests
توجه داشته باشید که استفاده کرده ایم -DskipTests
گزینه ای برای رد شدن از آزمایش ها زیرا برنامه ما سعی می کند به پایگاه داده ای متصل شود که هنوز وجود ندارد.
فایل JAR تولید شده برنامه Spring Boot ما یک بایگانی اجرایی است که شامل تمام اجزا و وابستگی های لازم برای اجرای برنامه مانند کد کامپایل شده، وابستگی ها، سرور جاسازی شده و برخی منابع اضافی است.
ما به سادگی می توانیم برنامه خود را با استفاده از دستور اجرا کنیم java -jar target/recipe-management-0.0.1-SNAPSHOT.jar
، این دقیقاً دستوری است که برای اجرای برنامه در داخل یک کانتینر به آن نیاز داریم.
4 – ایجاد تصویر برنامه (Dockerfile)
قسمت سرگرم کننده اینجاست!! اما ابتدا، فایل docker چیست؟
آ Dockerfile
یک سند متنی است که شامل مجموعهای از دستورالعملها برای ساختن یک تصویر است، این دستورالعملها میتواند استخراج و کپی کردن فایلها یا اجرای دستورات باشد…
خوب، بیایید یکی را در دایرکتوری ریشه برنامه خود ایجاد کنیم، باید نامگذاری شود Dockerfile
با “D” بزرگ (در واقع شما می توانید نام آن را هر چه می خواهید بگذارید، اما برای جلوگیری از سردرد در مراحل بعدی، اجازه دهید به این قرارداد احترام بگذاریم)
FROM openjdk:17
VOLUME /tmp
EXPOSE 8080
COPY target/recipe-management-0.0.1-SNAPSHOT.jar recipe.jar
ENTRYPOINT ["java","-jar","/recipe.jar"]
از openjdk:17 : هر تصویر داکر به یک تصویر پایه نیاز دارد که شامل سیستم عامل اصلی و سایر اجزای زمان اجرا باشد، و از آنجایی که ما یک برنامه Spring Boot داریم،
OpenJDK:17 Java Runtime Environment (JRE)
.VOLUME /tmp : (اختیاری) این دستورالعمل مشخص می کند که
/tmp
دایرکتوری در کانتینر docker به عنوان حجمی برای ذخیره فایلهای موقت و دادههای کش استفاده میشود… برای به اشتراک گذاشتن آن بین کانتینر داکر و سیستم فایل میزبان یا بین کانتینرها.EXPOSE 8080 : این دستورالعمل به Docker اطلاع می دهد که کانتینر به پورت گوش می دهد
8080
در زمان اجراCOPY target/recipe-management-0.0.1-SNAPSHOT.jar recipe.jar : این دستورالعمل کپی می کند
JAR
فایل به دایرکتوری کاری تصویر که به طور پیش فرض است/
، کپی شده استJAR
نام فایل به:recipe.jar
. (ما می توانیم دایرکتوری کاری را با استفاده از آن تغییر دهیمWORKDIR
دستورالعمل).نقطه ورود [“java”,”-jar”,”/recipe.jar”]: برای قسمت اول
ENTRYPOINT
برای پیکربندی دستوری استفاده میشود که هنگام راهاندازی یک کانتینر اجرا میشود، و همانطور که ممکن است حدس بزنید قسمت دوم دستور واقعی را مشخص میکند که برنامه ما را اجرا میکند.
خوب، ما آماده ایم چند آزمایش انجام دهیم!! بیایید ابتدا با اجرای دستور یک تصویر بسازیم:
docker build . -t recipe:v1
دستور بالا به داکر می گوید که تصویر را از فایل بسازد Dockerfile
در دایرکتوری فعلی ما می توانیم به صورت اختیاری استفاده کنیم -t
گزینه ای برای تعیین یک نام و یک برچسب برای تصویر ما.
اکنون تصویر خود را با نام و تگ با موفقیت ساخته ایم recipe:v1
.
با اجرای دستور می توانیم به سادگی بررسی کنیم که تصویر ما وجود دارد: docker images
:
5- ایجاد ظروف
در این بخش، با ایجاد یک کانتینر از برنامه خود و پیوند آن با a، همه چیز را کنار هم قرار می دهیم PostgreSQL
ظرف پایگاه داده از آنجایی که برنامه ما به یک پایگاه داده نیاز دارد.
ما میتوانیم به روشهای مختلفی به این هدف برسیم، اما رایجترین و واضحترین آن استفاده از آن است Docker Compose
.
Docker Compose
به ما این امکان را می دهد که همه کانتینرهای برنامه خود را تعریف و پیکربندی کنیم، آنها را به هم پیوند دهیم و وابستگی های بین آنها را در یک فایل واحد مشخص کنیم: docker-compose.yml
.
version: "3.8"
services:
psql-db:
image: postgres
container_name: psql-db
restart: always
environment:
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=1234
- POSTGRES_DB=recipe
ports:
- '5432:5432'
recipe:
container_name: recipe_app
build:
context: .
dockerfile: Dockerfile
ports:
- '8081:8080'
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://psql-db:5432/recipe
- SPRING_DATASOURCE_USERNAME=admin
- SPRING_DATASOURCE_PASSWORD=1234
- SPRING_JPA_HIBERNATE_DDL_AUTO=update
depends_on:
- psql-db
ابتدا، ما را تعریف می کنیم
yml
نسخهاولین ظرف را ایجاد کنید
psql-db
با استفاده از تصویر رسمی Postgresimage: postgres
.ما به ظرف یک نام می دهیم (اختیاری)
container_name: psql-db
.را
restart: always
پارامتر تضمین می کند که در صورت خرابی کانتینر به طور خودکار راه اندازی مجدد می شود.را
environment
گزینه می تواند از یک ظرف به ظرف دیگر برای پایگاه داده Postgres ما متفاوت باشد، ما باید یک کاربر پایگاه داده، رمز عبور و نام داشته باشیم…را
ports
گزینه پورت داخلی کانتینر را نقشه میکشد (در سمت چپ5432:
) به پورت میزبان5432
. این اجازه می دهد تا سایر سرویس های در حال اجرا بر روی همان هاست به پایگاه داده Postgres متصل شوندpsql-db
کانتینری که از آدرس IP و پورت میزبان استفاده می کند5432
.برای
recipe
ظرفی که تغییرات کوچکی ایجاد کرده ایم، استفاده کردیمbuild
به داکر بگوییم که یک تصویر جدید از ما بسازدDockerfile
.همچنین در بخش محیط، URL پایگاه داده، USERNAME و PASSWORD را مشخص کردیم. توجه داشته باشید که URL پایگاه داده حاوی نام است
Postgres
کانتینر و بندر آن:jdbc:postgresql://psql-db:5432/recipe
.سرانجام،
depends_on
گزینه می گویدrecipe
ظرفی که نباید تا زمانی که شروع شودpsql-db
ظرف در حال اجراست
اکنون ما آماده ایم تا ظروف خود را با استفاده از آن ایجاد کنیم docker-compose
.
(اگر از یک دستگاه لینوکس استفاده می کنید، باید این کار را انجام دهید Docker Compose را نصب کنید)
اگر شما docker-compose.yml
فایل را در ریشه پروژه خود اجرا کنید:
docker-compose up
# you can also use -d tag to start containers in the background.
از چند خط اول، واضح به نظر می رسد که داکر با موفقیت تصویر دستور غذا را بر اساس دستورالعمل های ارائه شده ساخته است Dockerfile
، ما را نیز ایجاد کرد psql-db
و recipe_app
ظروف و متصل است psql-db
به recipe_app
ظرف
بیایید یک http
درخواست کنید تا مطمئن شوید که همه چیز همانطور که انتظار می رود کار می کند!
نقطه پایانی http://localhost:8081/api/chefs است و توجه کنید که ما از پورت استفاده کردیم 8081
.
وویلا! برنامه کانتینری شده است! اکنون می توانیم آن را با هر کسی که داکر در دستگاه خود دارد به اشتراک بگذاریم.